最近遇到了一個死結問題, 記錄一下排查的過程.
SQL Server中的死結是DBA們經常會遇到的問題.
常有人混淆了死結和阻塞, 其實他們的區別還是很明顯的.
阻塞的一般原因可能是進程在等待事務提交,或者某個操作的完成, 是資源上的衝突, 只要等待足夠長的時間,是一定可以解決的; 但是死結是邏輯上的衝突, 也就是說無論等待多長的時間, 更換多麼強大的硬體, 都是無法解決死結的.
在SQL 2005以後的版本中, 用profiler去跟蹤死結的方式, 是最方便的. 使用 deadlock graph事件進行跟蹤, 可以產生非常直觀的圖示, 將死結發生的原因直接呈現出來.
以下是最近碰到的一次死結, 可以看到有兩個進程,78和74,在頁496081和頁5102680上發生了死結.
接下來看一下進程74當時在執行什麼語句
可以看出, 進程74當時執行了一條update語句
update atm_status set status='C', date='20120220', time='122016' where atm_no='97793914'
所以這個死結問題就可以這樣解釋: 進程74在更新表時,在頁496081上獲得了獨佔鎖定,並且請求頁5102680的更新鎖定, 而進程78卻獲得了頁5102680的獨佔鎖定,而請求頁496081的更新鎖定, 這樣兩個進程互相等待對方所擁有的資源, 於是就發生了死結.
但是接下來在跟開發人員討論的中, 遇到了一個問題. 表atm_status明明中只有一條記錄被這條update語句更新了, 並且通過dbcc page看到頁496081上確實儲存著要更新的那條記錄, 那為什麼進程74要獲得另一個頁上的更新鎖定呢?
仔細琢磨了一會兒, 查看了一下表的結構, 找到了原因.
表atm_status上是沒有任何索引的, 所以在做update時, 是用表掃描的方式, 逐頁的得到要更新的記錄. 在update的過程中,進程會對錶中的每個頁都請求更新鎖定, 如果得到了更新鎖定並且要更新的資料在此頁內,就把更新鎖定變為獨佔鎖定,進行更新. 這樣處理完一頁後,再依次處理表中的下一頁. 由於一條語句就是一個事務, 所以之前在其他頁上獲得的獨佔鎖定會一直保持著到語句執行結束. 這也正是進程74雖然只更新一條記錄, 卻在持有獨佔鎖定的同時, 還要在另一個頁上請求更新鎖定的原因.
分析出了原因, 解決這個死結的問題就比較容易了. 只要在列atm_no上加上合適的索引, 用索引搜尋替代表掃描的方式去更新資料, 那麼進程就只在需要更新的那一個頁上申請更新鎖定就行了, 不會再發生這樣的死結.
由此可見, 因為缺少合適的索引而導致的低效率查詢, 是產生死結的可能原因之一.