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