疯狂的“独占”行锁
原文地址:
http://www.brokenwire.net/bw/Programming/115/the-madness-of-exclusive-row-locks
相关阅读:(2011-10-13)
消失的共享锁
译文:
昨天我发现了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提示并不意味着只有你一个人能读这些数据行。