淺談 SQL 中的鎖(二)餘額問題的處理
http://blog.csdn.net/closurer/article/details/54288628
現在說說它的解決方法。
事務要正確地執行,就需要【隔離性】這個基本要素。更新餘額的語句之所以會偏離期望的結果,是由於查詢並沒有互斥,也就是沒有相互隔離:
--檢查使用者的餘額是否足夠
if(exists(select * from balance where id = 1 and value >= 1))
這個查詢語句在預設的情況下,會對查詢的資料放置共享鎖,當併發的時候,兩個共享鎖並不互斥,所以兩個併發的操作都得到餘額足夠的結果。
那麼,要解決這個問題,就需要兩個條件:
一、檢查餘額是否足夠的語句要互斥。
二、互斥要持續到完成餘額的更新。
於是,我把 SQL 修改成這樣:
--開始事務 begin transaction --檢查使用者的餘額是否足夠 if(exists(select * from balance(updlock) where id = 1 and value >= 1)) begin --延長處理時間 waitfor delay '0:00:10' --更新餘額 update balance set value = value - 1 end --提交事務 commit transaction --檢視處理結果 select * from balance
對應上面的條件,語句做了兩處修改:
一、在 select * from balance 後面添加了表提示 (updlock),指定對查詢的資料放置更新鎖。在併發的時候兩個更新鎖是會互斥的。
二、在整個更新語句的範圍指定使用同一個事務,使放置的更新鎖持續到整個操作結束。也就是在併發的時候,對同一個使用者的餘額更新,是序列執行的,只有當一個操作結束,另一個操作才會開始。
這樣就杜絕了餘額負數的問題。
在這個例子中,我並沒有更改 SQL Server 的預設隔離級別 read committed。SQL Server 還有兩個更高的隔離級別 repeatable read 和 serializable,什麼時候才用到呢?
下次再寫。