行锁与行锁出现的问题

 1.疯狂的“独占”行锁

 

原文地址:
http://www.brokenwire.net/bw/Programming/115/the-madness-of-exclusive-row-locks


译文:
昨天我发现了SQL SERVER一些确实很怪异的行为。我有一个案例我竟然可以读取被其他会话置了“独占”锁的记录。看到“独占”这个词,你想到的一定是:一个事务拥有独占行锁那么其它事务就不能读取该行了。但是这有特例:你可以读取被其它被别人独占锁定的行。
它花了我和同事很多时间,最终才发现到底是怎么回事。

为了重现这种行为,你需要一个测试表,表里有一些随机数据。

CREATE TABLE [MyTable]
 ([Col1] bigint PRIMARY KEY CLUSTERED, [Col2] bigint)
INSERT INTO [MyTable] ([Col1], [Col2]) VALUES (1,10)
INSERT INTO [MyTable] ([Col1], [Col2]) VALUES (2,20)
INSERT INTO [MyTable] ([Col1], [Col2]) VALUES (3,30)
INSERT INTO [MyTable] ([Col1], [Col2]) VALUES (4,40)
INSERT INTO [MyTable] ([Col1], [Col2]) VALUES (5,50)

只要数据库没有打开快照隔离,你可以将测试表放在任何数据库中,而且恢复模式也对它没有影响。
下面我们来运行一些查询,看看会发生什么。为了能正确地测试,你需要对同一测试表运行两个不同的会话。为了能一直持有已分配的锁,你需要启动一个事务、运行一些命令,但是千万不要结束事务。
首先在查询分析器的第一个窗口中(我们称之为会话1)查询表中的某一行,并且使用提示XLOCK和ROWLOCK获得一个独占的行锁。
会话 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
SELECT Col1 FROM [MyTable] WITH (XLOCK, ROWLOCK) WHERE [Col1] = 3

为了核实锁的情况,我们运行sp_locks来看看到底为会话1授予了哪些锁:

spid   dbid   ObjId       IndId  Type Resource                   Mode     Status
------ ------ ----------- ------ ---- -------------------------------- --------    ------
56     21     69575286    1      PAG  1:41                              IX       GRANT
56     21     69575286    1      KEY  (030075275214)           X        GRANT
56     21     69575286    0      TAB                                       IX       GRANT

 

你可以看到有一个“X”(独占)锁在表的第一个键上。(其他的锁是“IX”意向锁)。现在开始第2个连接,看看如果要读这条记录会发生什么?
会话 2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
SELECT Col1 FROM [MyTable] WHERE [Col1] = 3

我很希望这条语句被“挂住”,一直等到这条记录有效为止。但我吃惊地发现这条记录可以被非常顺利的读出。同时,sp_locks显示系统没有为这条指令分配任何其他锁,即使一个共享锁也没有。

如果你回滚会话2(为了撇清所有其它可能的情况)然后用表提示HOLDLOCK重新执行就会得到你想要的结果了:会话2中现在需要等待会话1中的事务完成了。

为了理解所发生的事,你需要回忆一下读提交隔离级别中的一条规则:可以读任何已经被提交的行。这里我们读的行时“干净的”(它没有被系统标为“脏的”),此时系统优化器会决定可以直接通过索引取数据而不需要检查锁的情况,所以表中甚至都不需要主键,只要有索引包含请求的数据,行锁就不需要了。

所以如果被锁住的记录并没有变化,被请求的列包含在索引中,那么从READ COMMITTTED隔离级别上就独占锁可能就没什么用了。

一种解决方法是在会话2的SELECT上加“HOLDLOCK”表提示。或者你也可以真的在会话1中更新记录,这样该记录就拥有独占锁了(而且还被标为“脏的”)。还有一种解决方法是用PAGLOCK锁住整个也而不仅仅是一行,此时独占锁会锁住该页中的所有行。

网上有人发了一篇帖子 讲述了同样的怪异行为,一个微软员工回复道:
“在SELECT语句中使用XLOCK并不能阻止读。这是因为SQL SERVER在读提交隔离级别上有一种特殊的优化,即检查行是否已被修改,如果未被修改则忽略XLOCK。因为在读提交隔离级别上这确实是可以接受的。”

可能最糟糕的事是没能在联机图书上找到这种行为。哪怕在section about table hints 中能有一个小小的说明也是好的。知识库KB324417 中(适用于SQL SERVER 2000)只有一点点的提示。综合上面所有的事实,优化器选择这种行为比较随便,因此你的SQL代码中的BUG是很难被发现的。


结论:
这花了我很多时间来找出到底发生了什么事。所以记住:在SELECT语句中使用XLOCK和ROWLOCK提示并不意味着只有你一个人能读这些数据行。

 

 

2.UPDATE 时, 如何避免数据定位处理被阻塞

 

问题描述:

数据库PUBS中的authors表,想锁定CITY为aaa的记录,为什么执行下面的命令后,CITY为bbb的记录也被锁定了,无法进行UPDATE.

BEGIN TRANSACTION    

    SELECT * FROM authors

    WITH (HOLDLOCK)

    WHERE city='aaa'

如何才能锁定CITY为AAA的记录,而且CITY为BBB的记录依然能SELECT和UPDATE?

 

问题分析:

应该不是被锁住,应该只是检索数据的时候,需要从aaa的记录扫描到bbb的记录,而aaa被锁住了,所以扫描无法往下进行,这样看起来似乎就是bbb也被锁住了。

当然,也有可能确实是被锁住了,SQL Server的锁定默认是行级的,如果你的资源不足,则可能导致锁自动升级为页级甚至表级锁,这样会导致更多的记录被锁定。

使用下面的语句, 如果能读出数据, 则多半是第1种情况.

SELECT *

FROM authors WITH (READPAST)

WHERE city='bbb'

如果读不出数据, 则一般是第2种情况.

 

问题解决方法:

让SELECT 和UPDATE 走不同的索引,这样在UPDATE 的时候,不用扫描已经锁定的数据就可以定义到记录,UPDATE 也就不会被阻塞了

指定索引用类似下面的语句:

SELECT *

FROM authors WITH (HOLDLOCK, INDEX=索引名)

WHERE city='aaa'

 

UPDATE A SET

    xx = xx

FROM authors A WITH (INDEX=索引名)

WHERE city='bbb'

当然,要保证仅扫描索引就可以定义到记录,否则可能还是会被阻塞。

  

补充

对于熟悉SQL Server锁的读者,可以通过 sp_lock,或者查询系统表 master.dbo.syslocks、master.dbo.syslockinfo来确定行为。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章