我们正在尝试更新/删除数十亿行表中的大量记录。由于这是一个受欢迎的表格,因此该表格的不同部分都有很多活动。任何较大的更新/删除活动都会被阻止较长时间(因为它正在等待获取所有行或页锁或表锁的锁),从而导致超时或花费多天才能完成任务。
因此,我们正在更改一次删除少量行的方法。但是我们要检查所选的(例如100或1000或2000行)当前是否已被其他进程锁定。
- 如果不是,则继续删除/更新。
- 如果它们被锁定,则移至下一组记录。
- 最后,回到开始并尝试更新/删除遗漏的内容。
这可行吗?
谢谢,ToC
我们正在尝试更新/删除数十亿行表中的大量记录。由于这是一个受欢迎的表格,因此该表格的不同部分都有很多活动。任何较大的更新/删除活动都会被阻止较长时间(因为它正在等待获取所有行或页锁或表锁的锁),从而导致超时或花费多天才能完成任务。
因此,我们正在更改一次删除少量行的方法。但是我们要检查所选的(例如100或1000或2000行)当前是否已被其他进程锁定。
这可行吗?
谢谢,ToC
Answers:
如果我正确理解了该请求,则目标是删除一批行,而与此同时,整个表中的行都将进行DML操作。目标是删除批处理;但是,如果锁定在该批处理定义的范围内的任何基础行,那么我们必须跳过该批处理并移至下一个批处理。然后,我们必须返回到以前未删除的所有批次,然后重试我们的原始删除逻辑。我们必须重复此循环,直到删除所有必需的行批次为止。
如前所述,使用READPAST提示和READ COMMITTED(默认)隔离级别是合理的,以便跳过可能包含阻塞行的范围。我将更进一步,建议使用SERIALIZABLE隔离级别和删除操作。
当使用可序列化事务隔离级别时,SQL Server使用键范围锁来保护Transact-SQL语句读取的记录集中隐式包括的行范围...在此处查找更多信息:https : //technet.microsoft.com /zh-CN/library/ms191272(v=SQL.105).aspx
借助删除操作,我们的目标是隔离一定范围的行,并确保在删除这些行时不会对这些行进行任何更改,也就是说,我们不希望幻像读取或插入。可序列化的隔离级别旨在解决此问题。
在演示解决方案之前,我想补充一点,我既不建议将数据库的默认隔离级别切换为SERIALIZABLE,也不建议我的解决方案是最好的。我只希望介绍它,看看我们可以从这里走到哪里。
一些客房整理注意事项:
为了开始实验,我将建立一个测试数据库,一个样本表,并在该表中填充2,000,000行。
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
在这一点上,我们将需要一个或多个索引,SERIALIZABLE隔离级别的锁定机制可以作用于该索引。
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
现在,让我们检查一下是否已创建了2,000,000行
SELECT
COUNT(*)
FROM
tbl;
因此,我们有了数据库,表,索引和行。因此,让我们设置用于删除操作的实验。首先,我们必须决定如何最好地创建典型的删除机制。
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
如您所见,我将显式事务置于while循环内。如果您想限制日志刷新,请随时将其放置在循环之外。此外,由于我们处于FULL恢复模型中,因此您可能希望在运行删除操作时更频繁地创建事务日志备份,以确保可以防止事务日志急剧增长。
因此,此设置有两个目标。首先,我想要我的键范围锁;因此,我尝试使批次尽可能小。我也不想负面影响我的“巨型”表的并发性;因此,我想带上我的锁,并尽可能快地离开它们。因此,我建议您减小批量大小。
现在,我想提供一个删除例程的简短示例。我们必须在SSMS中打开一个新窗口,并从表中删除一行。我将使用默认的READ COMMITTED隔离级别在隐式事务中执行此操作。
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
该行实际上被删除了吗?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
是的,它已被删除。
现在,为了查看我们的锁,让我们在SSMS中打开一个新窗口并添加一个或两个代码段。我正在使用Adam Mechanic的sp_whoisactive,可以在这里找到:sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
现在,我们准备开始。在新的SSMS窗口中,让我们开始一个显式事务,该事务将尝试重新插入我们删除的一行。同时,我们将取消删除操作。
插入代码:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
让我们从插入开始,然后是删除开始两个操作。我们可以看到键范围锁和排他锁。
插入生成了这些锁:
delete不休的删除/选择持有这些锁:
我们的插入正在按预期阻止我们的删除:
现在,让我们提交插入事务,看看发生了什么。
并按预期完成了所有交易。现在,我们必须检查插入是否为幻像,或删除操作是否也将其删除。
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
实际上,该插入已被删除;因此,不允许幻像插入。
因此,总而言之,我认为本练习的真正目的不是试图跟踪每个行,页或表级锁定,而是尝试确定批处理中的某个元素是否被锁定,因此需要我们执行删除操作来等待。这可能是提问者的意图;然而,这项任务是艰巨的,而且即使不是不可能的,也根本不切实际。真正的目标是确保一旦我们用自己的锁隔离了批次的范围之后再删除该批次,就不会出现不想要的现象。SERIALIZABLE隔离级别实现了此目标。关键是使您的零碎小块,控制事务日志并消除不希望的现象。
如果要提高速度,请不要构建无法分区的巨大的深表,因此无法使用分区切换来获得最快的结果。速度的关键是分区和并行性。苦难的关键是蚕食和活锁。
请让我知道你在想什么。
我进一步创建了SERIALIZABLE隔离级别的一些示例。它们应该在下面的链接中可用。
因此,我们正在更改一次删除少量行的方法。
删除细心的小批量或大块,这确实是一个好主意。我会在数据库之间添加一个小waitfor delay '00:00:05'
的数据库,取决于数据库的恢复模型-if FULL
,然后在批处理之间执行a log backup
和if SIMPLE
则执行a manual CHECKPOINT
以避免事务日志膨胀。
但是我们要检查所选的(例如100或1000或2000行)当前是否已被其他进程锁定。
您所讲的内容并非完全可以立即使用(请记住3个要点)。如果以上建议small batches + waitfor delay
不起作用(前提是您进行了适当的测试),则可以使用query HINT
。
不要使用NOLOCK
-参见kb / 308886,Itzik Ben-Gan 撰写的《SQL Server读取一致性问题》,《NOLOCK随处可见》,作者Aaron Bertrand和SQL Server NOLOCK提示及其他较差的主意。
READPAST
提示将对您的情况有所帮助。READPAST
提示的要点是-如果存在行级锁,则SQL Server将不会读取它。
指定数据库引擎不读取其他事务锁定的行。当
READPAST
指定,行级锁被跳过。也就是说,数据库引擎跳过行而不是阻塞当前事务,直到释放锁为止。
在有限的测试中,我发现在使用DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)
查询会话隔离级别并将其设置为READ COMMITTED
using时SET TRANSACTION ISOLATION LEVEL READ COMMITTED
,无论如何,它都是默认隔离级别,吞吐量确实很高。
总结最初在对该问题的评论中提供的其他方法。
NOWAIT
如果期望的行为是在遇到不兼容的锁后立即使整个块失败,则使用此方法。
指示数据库引擎在表上遇到锁定后立即返回消息。
NOWAIT
等效于SET LOCK_TIMEOUT 0
为特定表指定。在NOWAIT
当提示不工作TABLOCK
还包括提示。要在不使用TABLOCK
提示的情况下终止查询而无需等待,请SETLOCK_TIMEOUT 0;
改为在查询前添加前缀。
使用SET LOCK_TIMEOUT
实现了类似的结果,但有一个配置的超时:
指定语句等待释放锁的毫秒数。
当等待锁的时间超过超时值时,将返回错误。值为0表示根本不等待,一旦遇到锁定就返回一条消息。
假设我们有2个并行查询:
连接/会话1:将锁定该行= 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
连接/会话2:将忽略锁定的行= 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR连接/会话2:将引发异常
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
THROW 51000, 'Hi, a record is locked or does not exist.', 1;
尝试过滤这样的内容- 如果您想变得非常具体,它可能会变得很复杂。在BOL中查找sys.dm_tran_locks的描述
SELECT
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE
WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
您可以在删除时使用NoLOCK,如果行被锁定,则不会删除它们。它不是理想的,但可以为您解决问题。
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements.
,自2005年以来已弃用