假如兩個線程同時修改數據庫同一條記錄,就會導致后一條記錄覆蓋前一條,從而引發(fā)一些問題。
例如:
一個售票系統(tǒng)有一個余票數,客戶端每調用一次出票方法,余票數就減一。
情景:
總共300張票,假設兩個售票點,恰好在同一時間出票,它們做的操作都是先查詢余票數,然后減一。
一般的sql語句:
1
2
3
4
5
6
7
8
9
declare @count as int
begin tran
select @count=count from ttt
WAITFOR DELAY '00:00:05' --模擬并發(fā),故意延遲5秒
update ttt set count=@count-1
commit TRAN
SELECT * FROM ttt
問題就在于,同一時間獲取的余票都為300,每個售票點都做了一次更新為299的操作,導致余票少了1,而實際出了兩張票。
打開兩個查詢窗口,分別快速運行以上代碼即可看到效果。
定義解釋:
悲觀鎖:相信并發(fā)是絕大部分的,并且每一個線程都必須要達到目的的。
樂觀鎖:相信并發(fā)是極少數的,假設運氣不好遇到了,就放棄并返回信息告訴它再次嘗試。因為它是極少數發(fā)生的。
悲觀鎖解決方案:
1
2
3
4
5
6
7
declare @count as int
begin tran
select @count=count from tb WITH(UPDLOCK)
WAITFOR DELAY '00:00:05' --模擬并發(fā),故意延遲5秒
update tb set count=@count-1
commit tran
在查詢的時候加了一個更新鎖,保證自查詢起直到事務結束不會被其他事務讀取修改,避免產生臟數據。
從而可以解決上述問題。
樂觀鎖解決方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--首先給表加一列timestamp
ALTER TABLE ttt ADD timesFlag TIMESTAMP NOT null
然后更新時判斷這個值是否被修改
declare @count as int
DECLARE @flag AS TIMESTAMP
DECLARE @rowCount AS int
begin tran
select @count=COUNT,@flag=timesflag from ttt
WAITFOR DELAY '00:00:05'
update ttt set count=@count-1 WHERE timesflag=@flag --這里加了條件
SET @rowcount=@@ROWCOUNT --獲取被修改的行數
commit TRAN
--對行數進行判斷即可
IF @rowCount=1
PRINT '更新成功'
ELSE
PRINT '更新失敗'
這便是樂觀鎖的解決方案,可以解決并發(fā)帶來的數據錯誤問題,但不保證每一次調用更新都成功,可能會返回'更新失敗'
悲觀鎖和樂觀鎖
悲觀鎖一定成功,但在并發(fā)量特別大的時候會造成很長堵塞甚至超時,僅適合小并發(fā)的情況。
樂觀鎖不一定每次都修改成功,但能充分利用系統(tǒng)的并發(fā)處理機制,在大并發(fā)量的時候效率要高很多。