在Sql Server中,有没有一种方法可以检查选定的一组行是否被锁定?


21

我们正在尝试更新/删除数十亿行表中的大量记录。由于这是一个受欢迎的表格,因此该表格的不同部分都有很多活动。任何较大的更新/删除活动都会被阻止较长时间(因为它正在等待获取所有行或页锁或表锁的锁),从而导致超时或花费多天才能完成任务。

因此,我们正在更改一次删除少量行的方法。但是我们要检查所选的(例如100或1000或2000行)当前是否已被其他进程锁定。

  • 如果不是,则继续删除/更新。
  • 如果它们被锁定,则移至下一组记录。
  • 最后,回到开始并尝试更新/删除遗漏的内容。

这可行吗?

谢谢,ToC


2
您是否已将READPAST作为delete语句或NOWAIT的一部分进行了调查(使整个组失败)?其中之一可能适合您。msdn.microsoft.com/en-us/library/ms187373.aspx
肖恩说删除Sara Chipps

@SeanGallardy我没有考虑过这个想法,但是现在我会考虑。但是,有没有更简单的方法来检查特定行是否被锁定?谢谢。
ToC 2015年

3
您可能还会查看LOCK_TIMEOUT(msdn.microsoft.com/en-us/library/ms189470.aspx)。例如,亚当·马汉尼奇(Adam Machanic)的sp_whoisactive就是这样,如果在尝试收集执行计划时该过程被阻塞,该过程就不会等待太久。您可以设置一个短超时,甚至使用0值(“ 0表示根本不等待,一旦遇到锁定,就返回一条消息。”。)您可以将其与TRY / CATCH结合使用以捕获错误1222( “超出了锁定请求超时期限”),然后进行下一批操作。
2015年

@gpatterson有趣的方法。我也会尝试这个。
ToC 2015年

2
要回答,除非没有在应用程序中专门进行某些操作,否则没有简单的方法可以查看行是否被锁定。基本上,您首先可以使用具有lock_timeout设置的HOLDLOCK和XLOCK进行选择(这就是我原始注释中关于NOWAIT的内容,将超时设置为0)。如果您不明白,那您就会知道某些东西已被锁定。没有什么容易获得的说“是利用系数Z-锁定的东西在数据表Y行X”。我们可以看到表是否具有锁,或者页面/行/键/等是否具有锁,但是将其转换为查询中的特定行并不容易。
肖恩(Sean)说,请

Answers:


10

如果我正确理解了该请求,则目标是删除一批行,而与此同时,整个表中的行都将进行DML操作。目标是删除批处理;但是,如果锁定在该批处理定义的范围内的任何基础行,那么我们必须跳过该批处理并移至下一个批处理。然后,我们必须返回到以前未删除的所有批次,然后重试我们的原始删除逻辑。我们必须重复此循环,直到删除所有必需的行批次为止。

如前所述,使用READPAST提示和READ COMMITTED(默认)隔离级别是合理的,以便跳过可能包含阻塞行的范围。我将更进一步,建议使用SERIALIZABLE隔离级别和删除操作。

当使用可序列化事务隔离级别时,SQL Server使用键范围锁来保护Transact-SQL语句读取的记录集中隐式包括的行范围...在此处查找更多信息:https : //technet.microsoft.com /zh-CN/library/ms191272(v=SQL.105).aspx

借助删除操作,我们的目标是隔离一定范围的行,并确保在删除这些行时不会对这些行进行任何更改,也就是说,我们不希望幻像读取或插入。可序列化的隔离级别旨在解决此问题。

在演示解决方案之前,我想补充一点,我既不建议将数据库的默认隔离级别切换为SERIALIZABLE,也不建议我的解决方案是最好的。我只希望介绍它,看看我们可以从这里走到哪里。

一些客房整理注意事项:

  1. 我正在使用的SQL Server版本是Microsoft SQL Server 2012-11.0.5343.0(X64)
  2. 我的测试数据库正在使用完整恢复模型

为了开始实验,我将建立一个测试数据库,一个样本表,并在该表中填充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

在这一点上,我们将需要一个或多个索引,SE​​RIALIZABLE隔离级别的锁定机制可以作用于该索引。


-- 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隔离级别的一些示例。它们应该在下面的链接中可用。

删除操作

插入操作

平等操作-下一个键值的键范围锁定

平等操作-单例获取现有数据

平等操作-单项获取不存在的数据

不等式运算-范围和下一个键值的键范围锁定


9

因此,我们正在更改一次删除少量行的方法。

删除细心的小批量大块,这确实是一个好主意。我会在数据库之间添加一个小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 / 308886Itzik Ben-Gan 撰写的SQL Server读取一致性问题》,《NOLOCK随处可见》,作者Aaron BertrandSQL Server NOLOCK提示及其他较差的主意

READPAST提示将对您的情况有所帮助。READPAST提示的要点是-如果存在行级锁,则SQL Server将不会读取它。

指定数据库引擎不读取其他事务锁定的行。当READPAST指定,行级锁被跳过。也就是说,数据库引擎跳过行而不是阻塞当前事务,直到释放锁为止。

在有限的测试中,我发现在使用DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)查询会话隔离级别并将其设置为READ COMMITTEDusing时SET TRANSACTION ISOLATION LEVEL READ COMMITTED,无论如何,它都是默认隔离级别,吞吐量确实很高。


2

总结最初在对该问题的评论中提供的其他方法。


  1. NOWAIT如果期望的行为是在遇到不兼容的锁后立即使整个块失败,则使用此方法。

    NOWAIT文档中

    指示数据库引擎在表上遇到锁定后立即返回消息。NOWAIT等效于SET LOCK_TIMEOUT 0为特定表指定。在NOWAIT当提示不工作TABLOCK还包括提示。要在不使用TABLOCK提示的情况下终止查询而无需等待,请SETLOCK_TIMEOUT 0;改为在查询前添加前缀。

  2. 使用SET LOCK_TIMEOUT实现了类似的结果,但有一个配置的超时:

    SET LOCK_TIMEOUT文档中

    指定语句等待释放锁的毫秒数。

    当等待锁的时间超过超时值时,将返回错误。值为0表示根本不等待,一旦遇到锁定就返回一条消息。


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;

-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

只是好奇-为什么要投票?
rottengeek

-11

您可以在删除时使用NoLOCK,如果行被锁定,则不会删除它们。它不是理想的,但可以为您解决问题。

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True

7
如果我尝试,我的本地机器我得到的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年以来已弃用
汤姆N -团队莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.