悲观锁和乐观锁的详细分析
? ?悲觀鎖:
? ? ?顧名思義,悲觀鎖,正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。? ? ? ?悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。
? ? ?使用場景舉例:我們以mysql的存儲(chǔ)引擎 InnoDB為例(如果不采用鎖機(jī)制)
? ? ? ?商品表t_goods中有一個(gè)字段為status? 為1表示該商品沒有下單,為2表示該商品已經(jīng)被下單了,那么我們對(duì)某商品下單之前一定確保該商品未被下單,也就是說該商品的status=1
? ? ? ?我們對(duì)一個(gè)商品goods_id = 1 的下單實(shí)例
? ? ? ?1.先查出該商品的信息
? ? ? ?select status from t_goods where goods_id = 1
? ? ? ?2,然后根據(jù)商品信息生成訂單信息
? ? ? ? insert into t_order(order_id,goods_id) values (null,1)
? ? ? ?3.更新該商品的信息
? ? ? ? update t_goods set status = 2 where goods_id = 1
? ? ??
? ?上面的情況在高并發(fā)的情況會(huì)出現(xiàn)什么問題呢?
? ? ? ?想一些在這種高并發(fā)情況下如果我們不采取任何的鎖機(jī)制,是不是會(huì)出現(xiàn)我們?cè)谔幚磉@個(gè)事務(wù)(就是在下這個(gè)訂單的時(shí)候)別人是不是很可能已經(jīng)下了這個(gè)商品的訂單,status被修改為2了,可是我們完全不知情,繼續(xù)下單,就可能會(huì)造成該商品被下單兩次,所以這種是極其不安全的
? ? 因此在上述情況中我們可以采用悲觀鎖
? ? ? ?在上面的情況中,商品被查詢出來,有一個(gè)處理訂單的過程,我們可以使用悲觀鎖機(jī)制,當(dāng)我們查出這個(gè)goods信息后,就把當(dāng)前的數(shù)據(jù)鎖定起來,直到我們修改完畢再解鎖,所以在我們處理該goods的時(shí)候,就避免了第三者來修改
? 注意:使用悲觀鎖之前我們得關(guān)閉mysql的自動(dòng)提交功能,因?yàn)閙ysql是默認(rèn)開啟自動(dòng)提交功能的
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?set autocommit = 0;
? ? ??下面來簡單演示一下吧
? ? 1.開啟事務(wù)(三種方式都可以)
? ? ? ?begin;
? ? ? ?begin work;
? ? ? ?start transaction;
? ? ?2.查詢商品信息
? ? select status from t_goods where goods_id = 1??for update;
? ? 3.根據(jù)查詢來的商品信息生成訂單
? ? insert into t_order(order_id,goods_id) values (null,1);
? ? 4.修改該商品狀態(tài)為2
? ? update t_goods set status = 2 where goods_id = 1;
? ?5.提交
? ? commit;
? ? ?打開console1(明確指定主鍵,且查詢有數(shù)據(jù),采用Row-Level Lock )
? ? ?
? ? ? ?打開console2 (如果console1長時(shí)間不commit,這里會(huì)報(bào)錯(cuò))
? ? ??
? ? ?與普通查詢不一樣的是,我們使用了select…for update的方式,這樣就通過數(shù)據(jù)庫實(shí)現(xiàn)了悲觀鎖。此時(shí)在t_goods表中,goods_id為1的 那條數(shù)據(jù)就被我們鎖定了,其它的事務(wù)必須等本次事務(wù)提交之后才能執(zhí)行。這樣我們可以保證當(dāng)前的數(shù)據(jù)不會(huì)被其它事務(wù)修改
? ?注意:這里有個(gè)問題在是使用悲觀鎖的時(shí)候,如果第一個(gè)事務(wù)沒有commit我們是不能進(jìn)行該相同數(shù)據(jù)的寫操作的,但是讀操作分兩種情況,在事務(wù)中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一筆數(shù)據(jù)時(shí)會(huì)等待其它事務(wù)結(jié)束后才執(zhí)行,一般SELECT ... 則不受此影響。拿上面的實(shí)例來說,當(dāng)我執(zhí)行select status from t_goods where id=1 for update;后。我在另外的事務(wù)中如果再次執(zhí)行select status from t_goods where id=1 for update;則第二個(gè)事務(wù)會(huì)一直等待第一個(gè)事務(wù)的提交,此時(shí)第二個(gè)查詢處于阻塞的狀態(tài),但是如果我是在第二個(gè)事務(wù)中執(zhí)行select status from t_goods where id=1;則能正常查詢出數(shù)據(jù),不會(huì)受第一個(gè)事務(wù)的影響。
?
?
補(bǔ)充:MySQL select…for update的Row Lock與Table Lock
上面我們提到,使用select…for update會(huì)把數(shù)據(jù)給鎖住,不過我們需要注意一些鎖的級(jí)別,MySQL InnoDB默認(rèn)Row-Level Lock,所以只有「明確」地指定主鍵(id),MySQL 才會(huì)執(zhí)行Row lock (只鎖住被選取的數(shù)據(jù)) ,否則MySQL 將會(huì)執(zhí)行Table Lock (將整個(gè)數(shù)據(jù)表單給鎖住)。
如果明確有主鍵,沒有數(shù)據(jù),則沒有l(wèi)ock
? ? ?console1:沒數(shù)據(jù)查詢?yōu)榭?/span>
? ?
?console2:查詢結(jié)果為空,查詢無阻塞,說明console1沒有對(duì)數(shù)據(jù)執(zhí)行鎖定
如果沒有主鍵,采用table lock;
?console1:查詢正常
console2 :處于阻塞狀態(tài),如果console1長時(shí)間不提交,他就會(huì)報(bào)錯(cuò),說明這里對(duì)全表進(jìn)行了上鎖
?如果沒有定義主鍵,采用table lock;
console1:正常查詢
?
console2:查詢被阻塞,說明表被console1鎖住了
?以上就是關(guān)于數(shù)據(jù)庫主鍵對(duì)MySQL鎖級(jí)別的影響實(shí)例,需要注意的是,除了主鍵外,使用索引也會(huì)影響數(shù)據(jù)庫的鎖定級(jí)別
談到了MySQL悲觀鎖,但是悲觀鎖并不是適用于任何場景,它也有它存在的一些不足,因?yàn)楸^鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。如果加鎖的時(shí)間過長,其他用戶長時(shí)間無法訪問,影響了程序的并發(fā)訪問性,同時(shí)這樣對(duì)數(shù)據(jù)庫性能開銷影響也很大,特別是對(duì)長事務(wù)而言,這樣的開銷往往無法承受。所以與悲觀鎖相對(duì)的,我們有了樂觀鎖,具體參見下面介紹
樂觀鎖介紹:
?
? ?樂觀鎖( Optimistic Locking ) 相對(duì)悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測,如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。那么我們?nèi)绾螌?shí)現(xiàn)樂觀鎖呢,一般來說有以下2種方式:
?
1.使用數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn),這是樂觀鎖最常用的一種實(shí)現(xiàn)方式。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),一般是通過為數(shù)據(jù)庫表增加一個(gè)數(shù)字類型的 “version” 字段來實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)時(shí),將version字段的值一同讀出,數(shù)據(jù)每更新一次,對(duì)此version值加一。當(dāng)我們提交更新的時(shí)候,判斷數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來的version值進(jìn)行比對(duì),如果數(shù)據(jù)庫表當(dāng)前版本號(hào)與第一次取出來的version值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。用下面的一張圖來說明:
如上圖所示,如果更新操作順序執(zhí)行,則數(shù)據(jù)的版本(version)依次遞增,不會(huì)產(chǎn)生沖突。但是如果發(fā)生有不同的業(yè)務(wù)操作對(duì)同一版本的數(shù)據(jù)進(jìn)行修改,那么,先提交的操作(圖中B)會(huì)把數(shù)據(jù)version更新為2,當(dāng)A在B之后提交更新時(shí)發(fā)現(xiàn)數(shù)據(jù)的version已經(jīng)被修改了,那么A的更新操作會(huì)失敗
?2.樂觀鎖定的第二種實(shí)現(xiàn)方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個(gè)字段,名稱無所謂,字段類型使用時(shí)間戳(timestamp), 和上面的version類似,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫中數(shù)據(jù)的時(shí)間戳和自己更新前取到的時(shí)間戳進(jìn)行對(duì)比,如果一致則OK,否則就是版本沖突。
使用舉例:以MySQL InnoDB為例
還是拿之前的實(shí)例來舉:商品goods表中有一個(gè)字段status,status為1代表商品未被下單,status為2代表商品已經(jīng)被下單,那么我們對(duì)某個(gè)商品下單時(shí)必須確保該商品status為1。假設(shè)商品的id為1
?
輸出結(jié)果如下:
?由上述可知我們同時(shí)查出同一個(gè)版本的數(shù)據(jù),賦給不同的goods對(duì)象,然后先修改good1對(duì)象然后執(zhí)行更新操作,執(zhí)行成功。然后我們修改goods2,執(zhí)行更新操作時(shí)提示操作失敗
這樣我們就簡單實(shí)現(xiàn)了一個(gè)樂觀鎖機(jī)制!
轉(zhuǎn)載于:https://www.cnblogs.com/kyrieblog/p/11178577.html
總結(jié)
以上是生活随笔為你收集整理的悲观锁和乐观锁的详细分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASTreeView Demo:Add,
- 下一篇: (WPF)WPF要点之事件-深入浅出WP