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來確定行爲。