ToC*_*ToC 21 sql-server locking blocking
我们正在尝试更新/删除数十亿行表中的大量记录。由于这是一张受欢迎的桌子,因此这张桌子的不同部分有很多活动。任何大型更新/删除活动都被长时间阻塞(因为它正在等待获得所有行的锁或页锁或表锁),从而导致超时或需要多天才能完成任务。
因此,我们正在更改一次删除小批量行的方法。但是我们想检查选定的(比如 100、1000 或 2000 行)当前是否被不同的进程锁定。
这是可行的吗?
谢谢,ToC
oou*_*ire 11
如果我正确理解请求,目标是删除成批的行,同时对整个表的行进行 DML 操作。目标是删除一个批次;然而,如果包含在由所述批次定义的范围内的任何底层行被锁定,那么我们必须跳过该批次并移动到下一个批次。然后,我们必须返回到之前未删除的任何批次,并重试我们原来的删除逻辑。我们必须重复这个循环,直到所有需要的行的批次都被删除。
如前所述,使用 READPAST 提示和 READ COMMITTED(默认)隔离级别是合理的,以便跳过可能包含阻塞行的范围。我将更进一步,建议使用 SERIALIZABLE 隔离级别和蚕食删除。
SQL Server 使用 Key-Range 锁来保护隐式包含在 Transact-SQL 语句读取的记录集中的一系列行,同时使用可序列化事务隔离级别...在此处找到更多信息:https : //technet.microsoft.com /en-US/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
Run Code Online (Sandbox Code Playgroud)
此时,我们将需要一个或多个索引,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
Run Code Online (Sandbox Code Playgroud)
现在,让我们检查一下是否创建了 2,000,000 行
SELECT
COUNT(*)
FROM
tbl;
Run Code Online (Sandbox Code Playgroud)
所以,我们有我们的数据库、表、索引和行。所以,让我们建立一个实验来蚕食删除。首先,我们必须决定如何最好地创建一个典型的蚕食删除机制。
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;
Run Code Online (Sandbox Code Playgroud)
如您所见,我将显式事务放置在 while 循环中。如果您想限制日志刷新,请随意将其放置在循环之外。此外,由于我们处于 FULL 恢复模式,您可能希望在运行蚕食删除操作时更频繁地创建事务日志备份,以确保可以防止您的事务日志异常增长。
所以,我对这个设置有几个目标。首先,我想要我的钥匙范围锁;所以,我尽量保持批次尽可能小。我也不想对我的“巨大”表上的并发产生负面影响;所以,我想尽快把我的锁拿走。因此,我建议您减小批量大小。
现在,我想提供一个非常简短的示例,说明此删除例程的实际操作。我们必须在 SSMS 中打开一个新窗口并从我们的表中删除一行。我将使用默认的 READ COMMITTED 隔离级别在隐式事务中执行此操作。
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Run Code Online (Sandbox Code Playgroud)
这一行真的被删除了吗?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Run Code Online (Sandbox Code Playgroud)
是的,它被删除了。
现在,为了查看我们的锁,让我们在 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;
Run Code Online (Sandbox Code Playgroud)
现在,我们准备开始了。在一个新的 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;
Run Code Online (Sandbox Code Playgroud)
让我们开始两个操作,从插入开始,然后是删除。我们可以看到键范围锁和排他锁。
插入生成了这些锁:
啃删除/选择持有这些锁:
我们的插入按预期阻止了我们的删除:
现在,让我们提交插入事务,看看发生了什么。
正如预期的那样,所有交易都完成了。现在,我们必须检查插入是否是幻像或删除操作是否也将其删除。
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
Run Code Online (Sandbox Code Playgroud)
实际上,插入被删除了;所以,不允许幻像插入。
所以,总而言之,我认为这个练习的真正意图不是尝试跟踪每一行、页面或表级锁,并尝试确定批处理的元素是否被锁定,因此需要我们的删除操作等待。这可能是提问者的意图;然而,这项任务是艰巨的,如果不是不可能的话,基本上是不切实际的。真正的目标是确保一旦我们用我们自己的锁隔离了我们的批次范围,然后在删除批次之前,不会出现不需要的现象。SERIALIZABLE 隔离级别实现了这个目标。关键是要保持您的小点,控制您的事务日志,并消除不需要的现象。
如果您想要速度,那么不要构建无法分区的超深表,因此无法使用分区切换来获得最快的结果。速度的关键是分区和并行;痛苦的关键是啃咬和活锁。
请让我知道你的想法。
我创建了一些正在运行的 SERIALIZABLE 隔离级别的进一步示例。它们应该可以在下面的链接中找到。
因此,我们正在更改一次删除小批量行的方法。
这是一个非常好的删除小批量或块的主意。我会根据数据库的恢复模型添加一个小的waitfor delay '00:00:05'
并且取决于数据库的恢复模型 - 如果FULL
,则执行 a log backup
,SIMPLE
然后执行 amanual 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
使用SET TRANSACTION ISOLATION LEVEL READ COMMITTED
默认隔离级别时,吞吐量非常好。
mou*_*iin -11
您可以在删除时使用NoLOCK,如果行被锁定,它们将不会被删除。它并不理想,但可能对你有用。
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
6439 次 |
最近记录: |