探索Mysql鎖機制(一)——樂觀鎖&悲觀鎖

Milestone


本文需要閱讀時間大約在1小時,請抽出完整的時間來閱讀,一目十行,真心沒用

后面會按照下圖,分批次對Mysql的鎖和大家一起分享

image.png

前言


數據庫的鎖機制是并發控制的重要內容,是對程序控制數據一致性的補充,更細粒度的保障數據的一致性,而使各種共享資源在被并發訪問變得有序所設計的一種規則。下面主要針對我們常見的InnoDB和Myisam進行解析。

注:下文提到的分庫分表、fail-fast理念如果有需要,可以給大家分享下,在我廠內部應用場景。

花絮


小明是一家小作坊的屌絲程序員,工作3年,無房無車,有個女朋友叫"清風",一天一天又一天,過著無欲無求的屌絲生活,突然下雪的那天,聽說大廠某寶在招人:錢多事少妹紙穿的少、年終6月起步、有股票、上班不打卡、食堂超好、大神超多、可以直接對話18羅漢、老肖,甚至還可以撩馬爸爸!于是乎,小明血脈膨脹,氣血翻涌,熱淚盈眶,竟不能自已!閉關,苦練殺敵本領,2個月后,成功進入阿里,成為屌絲中的王者!于是乎,翻出祖傳寶典《程序員活下去的200個本事》之MYSQL篇。

有想來阿里的,可以聯系我,內推你哦~

樂觀鎖&悲觀鎖


樂觀并發控制和悲觀并發控制是并發控制采用的主要方法。樂觀鎖和悲觀鎖不僅在關系數據庫里應用,在Hibernate、Memcache等等也有相關概念。

1. 悲觀鎖

現在互聯網高并發的架構中,受到fail-fast思路的影響,悲觀鎖已經非常少見了。

悲觀鎖(Pessimistic Locking),悲觀鎖是指在數據處理過程,使數據處于鎖定狀態,一般使用數據庫的鎖機制實現。

1.1 數據表中的實現

在MySQL中使用悲觀鎖,必須關閉MySQL的自動提交,set autocommit=0,MySQL默認使用自動提交autocommit模式,也即你執行一個更新操作,MySQL會自動將結果提交。

set autocommit=0

舉個??栗子:
假設商品表中有一個字段quantity表示當前該商品的庫存量。假設有一件Dulex套套,其id為100,quantity=8個;如果不使用鎖,那么操作方法

如下:

//step1: 查出商品剩余量
 select quantity from items where id=100;
//step2: 如果剩余量大于0,則根據商品信息生成訂單
 insert into orders(id,item_id) values(null,100);
 //step3: 修改商品的庫存
 update Items set quantity=quantity-1 where id=100;

這樣子的寫法,在小作坊真的很正常,No Problems,但是在高并發環境下可能出現問題。

如下:


image.png

其實在①或者②環節,已經有人下單并且減完庫存了,這個時候仍然去執行step3,就造成了超賣

但是使用悲觀鎖,就可以解決這個問題,在上面的場景中,商品信息從查詢出來到修改,中間有一個生成訂單的過程,使用悲觀鎖的原理就是,當我們在查詢出items信息后就把當前的數據鎖定,直到我們修改完畢后再解鎖。那么在這個過程中,因為數據被鎖定了,就不會出現有第三者來對其進行修改了。而這樣做的前提是需要將要執行的SQL語句放在同一個事物中,否則達不到鎖定數據行的目的。

如下:

//step1: 查出商品狀態
select quantity from items where id=100 for update;
//step2: 根據商品信息生成訂單
insert into orders(id,item_id) values(null,100);
//step3: 修改商品的庫存
update Items set quantity=quantity-2 where id=100;

select...for update是MySQL提供的實現悲觀鎖的方式。此時在items表中,id為100的那條數據就被我們鎖定了,其它的要執行select quantity from items where id=100 for update的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。

1.2 擴展思考

需要注意的是,當我執行select quantity from items where id=100 for update后。如果我是在第二個事務中執行select quantity from items where id=100(不帶for update)仍能正常查詢出數據,不會受第一個事務的影響。另外,MySQL還有個問題是select...for update語句執行中所有掃描過的行都會被鎖上,因此在MySQL中用悲觀鎖務必須確定走了索引,而不是全表掃描,否則將會將整個數據表鎖住

悲觀鎖并不是適用于任何場景,它也存在一些不足,因為悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。如果加鎖的時間過長,其他用戶長時間無法訪問,影響了程序的并發訪問性,同時這樣對數據庫性能開銷影響也很大,特別是對長事務而言,這樣的開銷往往無法承受,這時就需要樂觀鎖。

在此和大家分享一下,在Oracle中,也存在select ... for update,和mysql一樣,但是Oracle還存在了select ... for update nowait,即發現被鎖后不等待,立刻報錯。

2. 樂觀鎖


樂觀鎖相對悲觀鎖而言,它認為數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測,如果發現沖突了,則讓返回錯誤信息,讓用戶決定如何去做。接下來我們看一下樂觀鎖在數據表和緩存中的實現。

2.1 數據表中的實現

利用數據版本號(version)機制是樂觀鎖最常用的一種實現方式。一般通過為數據庫表增加一個數字類型的 “version” 字段,當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值+1。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,如果數據庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期數據,返回更新失敗。

放個被用爛了的圖

image.png

舉個栗子??:

//step1: 查詢出商品信息
select (quantity,version) from items where id=100;
//step2: 根據商品信息生成訂單
insert into orders(id,item_id) values(null,100);
//step3: 修改商品的庫存
update items set quantity=quantity-1,version=version+1 where id=100 and version=#{version};

既然可以用version,那還可以使用時間戳字段,該方法同樣是在表中增加一個時間戳字段,和上面的version類似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本沖突。

需要注意的是,如果你的數據表是讀寫分離的表,當master表中寫入的數據沒有及時同步到slave表中時會造成更新一直失敗的問題。此時,需要強制讀取master表中的數據(將select語句放在事物中)。

即:把select語句放在事務中,查詢的就是master主庫了!

2.2 樂觀鎖的鎖粒度

樂觀鎖在我鳥系統中廣泛用于狀態同步,我們經常會遇到并發對一條物流訂單修改狀態的場景,所以此時樂觀鎖就發揮了巨大作用。

分享一個精心挑選樂觀鎖,以此縮小鎖范圍的case

商品庫存扣減時,尤其是在秒殺、聚劃算這種高并發的場景下,若采用version號作為樂觀鎖,則每次只有一個事務能更新成功,業務感知上就是大量操作失敗。

// 仍挑選以庫存數作為樂觀鎖
//step1: 查詢出商品信息
select (inventory) from items where id=100;
//step2: 根據商品信息生成訂單
insert into orders(id,item_id) values(null,100);
//step3: 修改商品的庫存
update items set inventory=inventory-1 where id=100 and inventory-1>0;

沒錯!你參加過的天貓、淘寶秒殺、聚劃算,跑的就是這條SQL,通過挑選樂觀鎖,可以減小鎖力度,從而提升吞吐~

樂觀鎖需要靈活運用

現在互聯網高并發的架構中,受到fail-fast思路的影響,悲觀鎖已經非常少見了。

2.3 擴展訓練

在阿里很多系統中都能看到常用的features、params等字段,這些字段如果不進行版本控制,在并發場景下非常容易出現信息覆蓋的問題。

比如:

線程 原始features 目標features
T-A a=1; a=1;b=1;
T-B a=1; a=1;c=1;

我們期望最終更新的結果為:

a=1;b=1;c=1;

此時若SQL寫成了

update    
    lg_order
set    
    features=#features#
where    
    order_id=#order_id#

那么隨著T-A和T-B的先后順序不同,我們得到的結果有可能會是a=1;b=1;或a=1;c=1;

若此時采用樂觀鎖,利用全局字段version進行處理,則會發現與lg_order的其他字段變更有非常高的沖突率,因為version字段是全局的

update    
    lg_order
set    
    features=#features#,    
    version=version+1
where    
    order_id=#order_id#    
    and version=#ori_version#

這種SQL會因為version的失敗而導致非常高的失敗率,當然咯,我其他字段也在并發變更呀~

怎么辦?


聰明的你會發現一般設計庫表的時,凡事擁有features類似字段的,都會有一個features_cc與之成對出現,很多廠內年輕一輩的程序員很少注意到這個字段,我們努力糾正過很久,現在應該好很多了。

features_cc的作用就是features的樂觀鎖版本的控制,這樣就規避了使用version與整個字段沖突的尷尬。

update    
    lg_order
set    
    features=#features#,    
    features_cc= features_cc +1
where    
    order_id=#order_id#    
    and features_cc =#ori_ features_cc#

這里需要注意的是,需要應用owner仔細review自己相關表的SQL,要求所有涉及到這個表features字段的變更都必須加上features_cc= features_cc +1進行計算,否則會引起并發沖突,平時要做好保護措施,不然很中意中標

在實際的環境中,這種高并發的場景中尤其多,大家思考一下是否自覺的加上了對features字段的樂觀鎖保護。

不過需要提出的是,做這種字段的精耕細作控制,是以提高維護成本作為代價的。

features、attribute這兩個字段我們花費了很長時間才BU同學達成共識和review代碼,要求用_cc來做版本控制。

若變更太頻繁,可以提出來單獨維護,做到冷熱數據分離。

今天就到這里吧,有些讀者說我寫的東西偏簡單,寫技術類的文章也是循序漸進的過程,后面會逐漸加深技術難度和廣度,并且把在大廠中是遇到的坑分享給大家。

喜歡的同學可以獻花了~

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容