假如兩個線程同時修改數據庫同一條記錄,就會致使後一條記錄覆蓋前一條,從而引起一些問題。sql
例如:數據庫
一個售票系統有一個餘票數,客戶端每調用一次出票方法,餘票數就減一。併發
情景: 線程
總共300張票,假設兩個售票點,剛好在同一時間出票,它們作的操做都是先查詢餘票數,而後減一。blog
通常的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,而實際出了兩張票。it
打開兩個查詢窗口,分別快速運行以上代碼便可看到效果。class
定義解釋:效率
悲觀鎖:相信併發是絕大部分的,而且每個線程都必需要達到目的的。date
樂觀鎖:相信併發是極少數的,假設運氣很差遇到了,就放棄並返回信息告訴它再次嘗試。由於它是極少數發生的。
悲觀鎖解決方案:
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 '更新失敗'
這即是樂觀鎖的解決方案,能夠解決併發帶來的數據錯誤問題,但不保證每一次調用更新都成功,可能會返回'更新失敗'
悲觀鎖和樂觀鎖
悲觀鎖必定成功,但在併發量特別大的時候會形成很長堵塞甚至超時,僅適合小併發的狀況。
樂觀鎖不必定每次都修改爲功,但能充分利用系統的併發處理機制,在大併發量的時候效率要高不少。