1樂觀鎖與悲觀鎖的區別(參考自網絡) 2 3 樂觀鎖的思路一般是表中增加版本字段,更新時where語句中增加版本的判斷,算是一種CAS(Compare And Swep)操作, 4 商品庫存場景中number起到了版本控制(相當於version)的作用( AND number=#{number})。 5 6 悲觀鎖之所以是悲觀,在於他認為本次操作會發生並發沖突,所以一開始就對商品加上鎖(SELECT … FOR UPDATE), 7 然后就可以安心的做判斷和更新,因為這時候不會有別人更新這條商品庫存。 8 9 從中我們也可以知道只要更新數據是依賴讀取的數據作為基礎條件的,就會有並發更新問題,需要樂觀鎖或者悲觀鎖取解決, 10 特別實在計數表現明顯。又比如在更新數據不依賴查詢的數據的就不會有問題, 11 例如修改用戶的名稱,多人同時修改,結果並不依賴於之前的用戶名字,這就不會有並發更新問題。 12 13 14 ================================ 15 1. 悲觀鎖 16 17 顧名思義就是很悲觀,每次拿數據都會認為別的線程會修改該數據,所以會給數據上鎖; 18 19 這樣搶到鎖的線程運行,取到數據做操作, 20 21 這期間其他線程想要訪問該數據時,都是阻塞block掛起狀態,操作不了; 22 23 核心就是不支持多並發,是單線程操作,通過搶占時間片的方式來搶鎖的使用權,把並發變成了串行。 24 25 共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程。 26 應用場景: 27 28 悲觀鎖適用於多寫的場景,保證線程安全和數據安全 29 30 mysql的行鎖、表鎖、讀鎖、寫鎖; 31 32 java中的synchronized。 33 34 ================================= 35 2. 樂觀鎖 36 37 顧名思義就是很樂觀,每次拿數據都認為別的線程不會修改數據,因此不會給數據上鎖; 38 39 但會在數據更新時判斷一下,在此期間其他線程有沒有對該數據做更新,最終通過多個線程的逐一更新獲取數據的最終值; 40 應用場景: 41 42 樂觀鎖適用於多讀的場景,獲取數據不再創建、銷毀鎖,減少了鎖的開銷,加大了數據的吞吐量, 43 44 Redis等非關系型數據庫 45 46 ps:Redis是單線程操作,把事務封閉在單一線程中,避免了線程的安全問題,所以里面沒有加悲觀鎖; 47 48 不過對於依賴多個Redis操作的復合操作來說,還是需要加鎖的,而且有可能是分布式鎖,也可以用LUA腳本,用任務隊列的方式解決多任務並發的問題。 49 50 51 52 53 ========================SQlserver上的悲觀鎖解決方案============================== 54 55 56 ===》》》悲觀鎖定解決方案 57 58 declare @CardNo varchar(20) 59 Begin Tran 60 61 -- 選擇一張未使用的卡 62 select top 1 @CardNo=F_CardNo 63 from Card with (UPDLOCK) where F_Flag=0 //---F_Flag=0表示沒有被人注冊使用這張卡 64 65 -- 延遲50秒,模擬並發訪問. 66 waitfor delay '000:00:50' 67 68 -- 把剛才選擇出來的卡進行注冊. 69 70 update Card 71 set F_Name=user, 72 F_Time=getdate(), 73 F_Flag=1 74 where F_CardNo=@CardNo 75 76 commit 77 78 注重其中的區別了嗎?with(updlock),是的,我們在查詢的時候使用了with (UPDLOCK)選項,在查詢記錄的時候 79 我們就對記錄加上了更新鎖,表示我們即將對次記錄進行更新.注重更新鎖和共享鎖是不沖突的,也就是其他用戶還可以 80 查詢此表的內容,但是和更新鎖和排它鎖是沖突的.所以其他的更新用戶就會阻塞.假如我們在另外一個窗口執行此代碼, 81 同樣不加waifor delay子句.兩邊執行完畢后,我們發現成功的注冊了兩張卡. 82 可能我們已經發現了悲觀鎖定的缺點:當一個用戶進行更新的事務的時候,其他更新用戶必須排隊等待,即使那個用戶更新的不是同一條記錄. 83 84 85 86 87 ======》》》樂觀鎖 88 樂觀鎖定解決方案 89 90 -- 首先我們在Card表里邊加上一列F_TimeStamp 列,該列是varbinary(8)類型.但是在更新的時候這個值會自動增長. 91 92 alter table Card add F_TimeStamp timestamp not null 93 94 -- 悲觀鎖定 95 declare @CardNo varchar(20) 96 declare @timestamp varbinary(8) 97 declare @rowcount int 98 99 Begin Tran 100 101 -- 取得卡號和原始的時間戳值 102 select top 1 @CardNo=F_CardNo, 103 @timestamp=F_TimeStamp 104 from Card 105 where F_Flag=0 106 107 -- 延遲50秒,模擬並發訪問. 108 waitfor delay '000:00:50' 109 110 -- 注冊卡,但是要比較時間戳是否發生了變化.假如沒有發生變化.更新成功.假如發生變化,更新失敗. 111 112 update Card 113 set F_Name=user, 114 F_Time=getdate(), 115 F_Flag=1 116 where F_CardNo=@CardNo and F_TimeStamp=@timestamp 117 set @rowcount=@@rowcount 118 if @rowcount=1 119 begin 120 print '更新成功!' 121 commit 122 end 123 else if @rowcount=0 124 begin 125 if exists(select 1 from Card where F_CardNo=@CardNo) 126 begin 127 print '此卡已經被另外一個用戶注冊!' 128 rollback tran 129 end 130 else 131 begin 132 print '並不存在此卡!' 133 rollback tran 134 end 135 end 136 137 在另外一個窗口里邊執行沒有waitfor的代碼,注冊成功后,返回原來的窗口,我們就會發現到時間后它顯示的提示是此卡以 138 被另外一個用戶注冊的提示.很明顯,這樣我們也可以避免兩個用戶同時注冊一張卡的現象的出現.同時, 139 使用這種方法的另外一個好處是沒有使用更新鎖,這樣增加的系統的並發處理能力. 140 141 總結:上邊我具體介紹了樂觀鎖定和悲觀鎖定的使用方法,在實際生產環境里邊,假如並發量不大, 142 我們完全可以使用悲觀鎖定的方法,因為這種方法使用起來非常方便和簡單.但是假如系統的並發非常大的話, 143 悲觀鎖定會帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法.