在SQL Server中,读锁如何工作?


17

假设我有以下长期运行的查询

UPDATE [Table1]
SET [Col1] = 'some value'
WHERE [Col2] -- some clause which selects thousands of rows

并假设上面的查询运行时执行了以下查询

SELECT *
FROM [Table1]

在第一个查询完成之前,第一个查询是否会阻止第二个查询运行?如果是这样,则第一个查询是否阻止第二个查询在所有行上运行,或者仅在WHERE子句中涉及的行上运行?

编辑:

假设第二个查询是

SELECT [Col1], [Col2]
FROM [Table1]
WHERE [Col2] -- some clause whose matching elements overlap those from
             -- the clause in the first query and which has additional matching elements

Answers:


14

我建议您阅读《了解SQL Server如何执行查询》,其中有关于读取和写入工作以及锁定如何工作的解释。

10000ft视图如下:

  • 读取操作员读取数据之前获取对其读取数据的共享锁
  • 写操作符在修改数据之前获取对其修改的数据的排他锁
  • 数据锁只是字符串,例如。由数据库和对象限定范围的键的哈希值。
  • 锁管理器根据锁兼容性矩阵维护所有授予的锁的列表,并检测不兼容性
  • 不兼容的请求将被挂起,直到释放阻止它们的不兼容的授权为止
  • 操作员使用锁层次结构声明意图在更高级别(页或表级别,而忽略分区级别的选项)读取或更新数据。这使操作员可以锁定整个表,而不必锁定每一行
  • 锁定生存期和范围锁定用于强制执行更高的隔离级别

这实际上只是冰山一角。这个主题是广阔的。在您的示例中,没有人能回答您有关实际被锁定的问题,因为这取决于许多因素。当然,任何应用程序都不应发出a,SELECT * FROM Table1 因为它缺少WHERE子句并且正在使用*。这些是不正确的做法,因为它们尤其会导致锁定竞争。

如果遇到读锁定和写锁定,则需要查看行版本控制和快照隔离。阅读了解基于行版本控制的隔离级别


如果我需要一张表的所有内容(比如说我只有14行)怎么办?SELECT * FROM Table1如果这正是我所需要的,怎么做是不好的做法?
方位角

1
*一个单独的做法是不好的做法,因为当表结构更改时,应用程序通常会中断(结果中出现意外的列)。
Remus Rusanu

3

编辑:正如@MaxVernon所指出的,以下绝不是使用NOLOCK的建议,我非常应该刚才提到将事务级别设置为,READ UNCOMMITED并让负面含义站在那里而不是首先提出NOLOCK来。因此,如最初发布的:

快速简单的方法是:“是,除非指定了特定的索引提示(NOLOCK,有时称为“脏读”),或者第二个查询的事务隔离级别设置为READ UNCOMMITED(相同地操作),否则第一个查询将阻止第二个查询,不,不是的。”

响应于问题中提供的其他细节,该细节WITH要求在第二个元素上包含一个子句(SELECT互斥或以其他方式互斥),因此两个查询之间的交互将基本相同。

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'Foo'
                    AND type = 'U' )
BEGIN
    --DROP TABLE dbo.Foo;
    CREATE TABLE dbo.Foo
    (
        Foo_PK          BIGINT IDENTITY( 1, 1 ) NOT NULL,
                            PRIMARY KEY ( Foo_PK ),
        Bar             BIT,
        x               BIT,
        y               BIT,
        z               BIT
    );

    CREATE NONCLUSTERED INDEX IX_Foo_x
        ON  dbo.Foo ( x );

    INSERT INTO dbo.Foo ( Bar, x, y, z )
    VALUES ( 1, 1, 1, 1 ), ( 0, 0, 0, 0 );
END;    
GO

BEGIN TRANSACTION;

UPDATE  dbo.Foo
    SET y = 0
WHERE   x = 1;

-- COMMIT TRANSACTION;

在一个单独的会话中,运行以下命令:

SELECT  *
FROM    dbo.Foo WITH ( NOLOCK );
GO

SELECT  *
FROM    dbo.Foo;

您可以通过运行来检查当前持有的锁sp_lock,最好在另一个单独的会话中:

EXECUTE dbo.sp_lock;

您应该看到KEYspid在X(独占)模式下执行插入事务的spid持有一个类型锁,不要与其他IX(Intent-Exclusive)锁混淆。所述锁定文档指示的同时KEY锁是范围特异性的,它也可以防止其它事务插入或通过改变数据更新受影响的列包含在其中,以便它可以落入该范围内的原始查询的范围内。由于持有的锁本身是排他的,因此第一个查询将阻止任何其他并发事务访问资源。实际上,该列的所有行均被锁定,无论它们是否在第一个查询指定的范围内。

因此S,第二个会话持有的锁将WAIT一直保持到X锁被清除为止,从而防止在第二个会话完成其读取操作之前,从另一个并发spid对该资源获取另一个X(或U)锁,从而证明该S锁的存在。

现在为清楚起见进行编辑:除非我从这里提到风险的简短描述中弄错了读物是什么,否则... 编辑3:我只是意识到我没有考虑写一个as的背景检查点的影响。尚未提交到磁盘的事务,所以是的,我的解释有误导性。

在第二个查询中,第一批可以(并且在这种情况下,将)返回未提交的数据。以默认事务隔离级别运行的第二批READ COMMITED将仅在第一个会话中完成提交或回滚后返回。

从这里您可以查看查询计划和关联的锁级别,但是更好的是,您可以在此处阅读有关SQL Server中锁的所有信息。


1
WITH (NOLOCK)在这种情况下,警告使用会有所帮助。请参阅brentozar.com/archive/2011/11/…brentozar.com/archive/2013/02/…了解更多信息。
Max Vernon 2014年

3
哦,WITH (NOLOCK)提示不会从尚未提交的内存中返回脏页。它实际上从表中读取行(无论是在磁盘上还是在内存中缓存),而不会阻止编写器更新表或将行添加到表所使用的页面。
Max Vernon 2014年

2
我很困惑。如果回答“第一个查询是否阻止第二个查询运行?” 是“否”,第二个问题的答案怎么会是“是”?您能否弄清楚要回答的问题,并扩展答案?
所有行业的乔恩2014年

编辑嘉豪,对不起大家!让我知道是否还有其他不清楚的地方!
Avarkx 2014年
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.