悲觀鎖和樂觀鎖

悲觀鎖

悲觀鎖(Pessimistic Lock), 顧名思義吕座,就是很悲觀惫皱,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改健田,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會block直到它拿到鎖晃痴。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖财忽,表鎖等倘核,讀鎖,寫鎖等即彪,都是在做操作之前先上鎖笤虫。

使用場景

以MySQL InnoDB為例:

商品goods表中有一個字段status,status為1代表商品未被下單,status為2代表商品已經(jīng)被下單琼蚯,那么我們對某個商品下單時必須確保該商品status為1酬凳。假設(shè)商品的id為1。

如果不采用鎖遭庶,那么操作方法如下:
//1.查詢出商品信息
select status from t_goods where id=1;
//2.根據(jù)商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;

上面這種場景在高并發(fā)訪問的情況下很可能會出現(xiàn)問題宁仔。
前面已經(jīng)提到,只有當(dāng)goods status為1時才能對該商品下單峦睡,上面第一步操作中翎苫,查詢出來的商品status為1。但是當(dāng)我們執(zhí)行第三步Update操作的時候榨了,有可能出現(xiàn)其他人先一步對商品下單把goods status修改為2了煎谍,但是我們并不知道數(shù)據(jù)已經(jīng)被修改了,這樣就可能造成同一個商品被下單2次龙屉,使得數(shù)據(jù)不一致呐粘。所以說這種方式是不安全的。
 
使用悲觀鎖來實現(xiàn):
在上面的場景中转捕,商品信息從查詢出來到修改作岖,中間有一個處理訂單的過程,使用悲觀鎖的原理就是五芝,當(dāng)我們在查詢出goods信息后就把當(dāng)前的數(shù)據(jù)鎖定痘儡,直到我們修改完畢后再解鎖。那么在這個過程中枢步,因為goods被鎖定了沉删,就不會出現(xiàn)有第三者來對其進(jìn)行修改了。
 
注:要使用悲觀鎖醉途,我們必須關(guān)閉mysql數(shù)據(jù)庫的自動提交屬性丑念,因為MySQL默認(rèn)使用autocommit模式,也就是說结蟋,當(dāng)你執(zhí)行一個更新操作后脯倚,MySQL會立刻將結(jié)果進(jìn)行提交。
 
我們可以使用命令設(shè)置MySQL為非autocommit模式:
set autocommit=0;
設(shè)置完autocommit后嵌屎,我們就可以執(zhí)行我們的正常業(yè)務(wù)了推正。具體如下:
//0.開始事務(wù)
begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品信息
select status from t_goods where id=1 for update;
//2.根據(jù)商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.提交事務(wù)
commit;/commit work;
 

上面的第一步我們執(zhí)行了一次查詢操作:select status from t_goods where id=1 for update;
與普通查詢不一樣的是,我們使用了select…for update的方式宝惰,這樣就通過數(shù)據(jù)庫實現(xiàn)了悲觀鎖植榕。此時在t_goods表中,id為1的 那條數(shù)據(jù)就被我們鎖定了尼夺,其它的事務(wù)必須等本次事務(wù)提交之后才能執(zhí)行尊残。這樣我們可以保證當(dāng)前的數(shù)據(jù)不會被其它事務(wù)修改炒瘸。

備注:使用select for update會把數(shù)據(jù)給鎖住,不過我們需要注意一些鎖的級別寝衫,MySQL InnoDB默認(rèn)行級鎖顷扩。行級鎖都是基于索引的,如果一條SQL語句用不到索引是不會使用行級鎖的慰毅,會使用表級鎖把整張表鎖住隘截,這點需要注意。

優(yōu)點與不足

悲觀并發(fā)控制實際上是“先取鎖再訪問”的保守策略汹胃,為數(shù)據(jù)處理的安全提供了保證婶芭。但是在效率方面,處理加鎖的機制會讓數(shù)據(jù)庫產(chǎn)生額外的開銷着饥,還有增加產(chǎn)生死鎖的機會犀农;另外,在只讀型事務(wù)處理中由于不會產(chǎn)生沖突宰掉,也沒必要使用鎖呵哨,這樣做只能增加系統(tǒng)負(fù)載;還有會降低了并行性贵扰,一個事務(wù)如果鎖定了某行數(shù)據(jù)仇穗,其他事務(wù)就必須等待該事務(wù)處理完才可以處理那行數(shù)據(jù)流部。

樂觀鎖

樂觀鎖(Optimistic Lock), 顧名思義戚绕,就是很樂觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改枝冀,所以不會上鎖舞丛,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號等機制果漾。樂觀鎖適用于多讀的應(yīng)用類型球切,這樣可以提高吞吐量,像數(shù)據(jù)庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖绒障。

相對于悲觀鎖吨凑,在對數(shù)據(jù)庫進(jìn)行處理的時候,樂觀鎖并不會使用數(shù)據(jù)庫提供的鎖機制户辱。一般的實現(xiàn)樂觀鎖的方式就是記錄數(shù)據(jù)版本鸵钝。

數(shù)據(jù)版本:

為數(shù)據(jù)增加的一個版本標(biāo)識。當(dāng)讀取數(shù)據(jù)時庐镐,將版本標(biāo)識的值一同讀出恩商,數(shù)據(jù)每更新一次,同時對版本標(biāo)識進(jìn)行更新必逆。當(dāng)我們提交更新的時候怠堪,判斷數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息與第一次取出來的版本標(biāo)識進(jìn)行比對揽乱,如果數(shù)據(jù)庫表當(dāng)前版本號與第一次取出來的版本標(biāo)識值相等,則予以更新粟矿,否則認(rèn)為是過期數(shù)據(jù)凰棉。

實現(xiàn)數(shù)據(jù)版本有兩種方式,第一種是使用版本號嚷炉,第二種是使用時間戳渊啰。

使用版本號實現(xiàn)樂觀鎖

使用版本號時,可以在數(shù)據(jù)初始化時指定一個版本號申屹,每次對數(shù)據(jù)的更新操作都對版本號執(zhí)行+1操作绘证。并判斷當(dāng)前版本號是不是該數(shù)據(jù)的最新的版本號。

1.查詢出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根據(jù)商品信息生成訂單
3.修改商品status為2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

優(yōu)點與不足

樂觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競爭(data race)的概率是比較小的哗讥,因此盡可能直接做下去嚷那,直到提交的時候才去鎖定,所以不會產(chǎn)生任何鎖和死鎖杆煞。但如果直接簡單這么做魏宽,還是有可能會遇到不可預(yù)期的結(jié)果,例如兩個事務(wù)都讀取了數(shù)據(jù)庫的某一行决乎,經(jīng)過修改以后寫回數(shù)據(jù)庫队询,這時就遇到了問題。

行級鎖與死鎖

MyISAM中是不會產(chǎn)生死鎖的构诚,因為MyISAM總是一次性獲得所需的全部鎖蚌斩,要么全部滿足,要么全部等待范嘱。而在InnoDB中送膳,鎖是逐步獲得的,就造成了死鎖的可能丑蛤。

在MySQL中叠聋,行級鎖并不是直接鎖記錄,而是鎖索引受裹。索引分為主鍵索引和非主鍵索引兩種碌补,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引棉饶;如果一條語句操作了非主鍵索引厦章,MySQL會先鎖定該非主鍵索引,再鎖定相關(guān)的主鍵索引砰盐。在UPDATE闷袒、DELETE操作時,MySQL不僅鎖定WHERE條件掃描過的所有索引記錄岩梳,而且會鎖定相鄰的鍵值囊骤,即所謂的next-key locking晃择。

當(dāng)兩個事務(wù)同時執(zhí)行,一個鎖住了主鍵索引也物,在等待其他相關(guān)索引宫屠。另一個鎖定了非主鍵索引,在等待主鍵索引滑蚯。這樣就會發(fā)生死鎖浪蹂。

發(fā)生死鎖后,InnoDB一般都可以檢測到告材,并使一個事務(wù)釋放鎖回退坤次,另一個獲取鎖完成事務(wù)。

有多種方法可以避免死鎖斥赋,這里只介紹常見的三種

  1. 如果不同程序會并發(fā)存取多個表缰猴,盡量約定以相同的順序訪問表,可以大大降低死鎖機會疤剑。

  2. 在同一個事務(wù)中滑绒,盡可能做到一次鎖定所需要的所有資源,減少死鎖產(chǎn)生概率隘膘;

  3. 對于非常容易產(chǎn)生死鎖的業(yè)務(wù)部分疑故,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產(chǎn)生的概率弯菊;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纵势,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子误续,更是在濱河造成了極大的恐慌吨悍,老刑警劉巖扫茅,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋嵌,死亡現(xiàn)場離奇詭異,居然都是意外死亡葫隙,警方通過查閱死者的電腦和手機栽烂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恋脚,“玉大人腺办,你說我怎么就攤上這事≡忝瑁” “怎么了怀喉?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長船响。 經(jīng)常有香客問我躬拢,道長躲履,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任聊闯,我火速辦了婚禮工猜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菱蔬。我一直安慰自己篷帅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布拴泌。 她就那樣靜靜地躺著魏身,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚪腐。 梳的紋絲不亂的頭發(fā)上叠骑,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天,我揣著相機與錄音削茁,去河邊找鬼宙枷。 笑死,一個胖子當(dāng)著我的面吹牛茧跋,可吹牛的內(nèi)容都是我干的慰丛。 我是一名探鬼主播,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼瘾杭,長吁一口氣:“原來是場噩夢啊……” “哼诅病!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粥烁,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤贤笆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讨阻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芥永,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年钝吮,在試婚紗的時候發(fā)現(xiàn)自己被綠了埋涧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,989評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡奇瘦,死狀恐怖棘催,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耳标,我是刑警寧澤醇坝,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站次坡,受9級特大地震影響呼猪,放射性物質(zhì)發(fā)生泄漏呀袱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一郑叠、第九天 我趴在偏房一處隱蔽的房頂上張望夜赵。 院中可真熱鬧,春花似錦乡革、人聲如沸寇僧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘁傀。三九已至,卻和暖如春视粮,著一層夾襖步出監(jiān)牢的瞬間细办,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工蕾殴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笑撞,地道東北人。 一個月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓钓觉,卻偏偏與公主長得像茴肥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荡灾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,995評論 2 361

推薦閱讀更多精彩內(nèi)容