MySql-兩階段加鎖協(xié)議

顧名思義,行鎖就是針對(duì)數(shù)據(jù)表中行記錄的鎖。這很好理解,比如事務(wù) A 更新了一行,而這時(shí)候事務(wù) B 也要更新同一行,則必須等事務(wù) A 的操作完成后才能進(jìn)行更新。

在實(shí)際情況下,SQL是千變?nèi)f化、條數(shù)不定的,數(shù)據(jù)庫(kù)很難在事務(wù)中判定什么是加鎖階段,什么是解鎖階段。于是引入了S2PL(Strict-2PL),即: 在事務(wù)中只有提交(commit)或者回滾(rollback)時(shí)才是解鎖階段,其余時(shí)間為加鎖階段。

舉個(gè)例子。在下面的操作序列中,事務(wù) B 的 update 語(yǔ)句執(zhí)行時(shí)會(huì)是什么現(xiàn)象呢?假設(shè)字段 id 是表 t 的主鍵。

實(shí)際上事務(wù) B 的 update 語(yǔ)句會(huì)被阻塞,直到事務(wù) A 執(zhí)行 commit 之后,事務(wù) B 才能繼續(xù)執(zhí)行。事務(wù) A 持有的兩個(gè)記錄的行鎖,都是在 commit 的時(shí)候才釋放的。在 InnoDB 事務(wù)中,行鎖是在需要的時(shí)候才加上的,但并不是不需要了就立刻釋放,而是要等到事務(wù)結(jié)束時(shí)才釋放。這個(gè)就是兩階段鎖協(xié)議。

兩階段加鎖對(duì)性能的影響,下面兩種不同的扣減庫(kù)存的方案:

方案1:begin;// 扣減庫(kù)存update t_inventory set count=count-5 where id= ${id} and count>=5;// 鎖住用戶賬戶表select * from t_user_account where user_id=123 for update;// 插入訂單記錄insert into t_trans; commit;

方案2:begin;// 鎖住用戶賬戶表select * from t_user_account where user_id=123 for update;// 插入訂單記錄insert into t_trans;// 扣減庫(kù)存update t_inventory set count=count-5 where id=${id} and count>=5;commit;

兩者方案的時(shí)序如下圖所示:

由于庫(kù)存往往是最重要的熱點(diǎn),是整個(gè)系統(tǒng)的瓶頸。那么如果采用第二種方案的話,tps應(yīng)該理論上能夠提升3rt/rt=3倍。這還僅僅是業(yè)務(wù)就只有三條SQL的情況下,多一條sql就多一次rt,就多一倍的時(shí)間。

根據(jù)兩階段鎖協(xié)議,不論你怎樣安排語(yǔ)句順序,所有的操作需要的行鎖都是在事務(wù)提交的時(shí)候才釋放的。所以,如果你把更新庫(kù)存 安排在最后,那么庫(kù)存這一行的鎖時(shí)間就最少。這就最大程度地減少了事務(wù)之間的鎖等待,提升了并發(fā)度。

值得注意的是:

在更新到數(shù)據(jù)庫(kù)的那個(gè)時(shí)間點(diǎn)才算鎖成功,提交到數(shù)據(jù)庫(kù)的時(shí)候才算解鎖成功,這兩個(gè)round_trip的前半段是不會(huì)計(jì)算在內(nèi)的:


從上面的例子中,可以看出,需要把最熱點(diǎn)的記錄,放到事務(wù)最后,這樣可以顯著的提高吞吐量。更進(jìn)一步:越熱點(diǎn)記錄離事務(wù)的終點(diǎn)越近(無(wú)論是commit還是rollback)

避免死鎖

這也是任何SQL加鎖不可避免的。上文提到了按照記錄Key的熱度在事務(wù)中倒序排列。 那么寫代碼的時(shí)候任何可能并發(fā)的SQL都必須按照這種順序來(lái)處理,不然會(huì)造成死鎖。如下圖所示:?

但是當(dāng)業(yè)務(wù)場(chǎng)景復(fù)雜,依然會(huì)有死鎖的可能,當(dāng)出現(xiàn)死鎖以后,有兩種策略:

一種策略是,直接進(jìn)入等待,直到超時(shí)。這個(gè)超時(shí)時(shí)間可以通過參數(shù) innodb_lock_wait_timeout 來(lái)設(shè)置。

另一種策略是,發(fā)起死鎖檢測(cè),發(fā)現(xiàn)死鎖后,主動(dòng)回滾死鎖鏈條中的某一個(gè)事務(wù),讓其他事務(wù)得以繼續(xù)執(zhí)行。將參數(shù) innodb_deadlock_detect 設(shè)置為 on,表示開啟這個(gè)邏輯。

在 InnoDB 中,innodb_lock_wait_timeout 的默認(rèn)值是 50s,意味著如果采用第一個(gè)策略,當(dāng)出現(xiàn)死鎖以后,第一個(gè)被鎖住的線程要過 50s 才會(huì)超時(shí)退出,然后其他線程才有可能繼續(xù)執(zhí)行。對(duì)于在線服務(wù)來(lái)說,這個(gè)等待時(shí)間往往是無(wú)法接受的。但是,我們又不可能直接把這個(gè)時(shí)間設(shè)置成一個(gè)很小的值,比如 1s。這樣當(dāng)出現(xiàn)死鎖的時(shí)候,確實(shí)很快就可以解開,但如果不是死鎖,而是簡(jiǎn)單的鎖等待呢?所以,超時(shí)時(shí)間設(shè)置太短的話,會(huì)出現(xiàn)很多誤傷。

所以,正常情況下我們還是要采用第二種策略,即:主動(dòng)死鎖檢測(cè),而且 innodb_deadlock_detect 的默認(rèn)值本身就是 on。主動(dòng)死鎖檢測(cè)在發(fā)生死鎖的時(shí)候,是能夠快速發(fā)現(xiàn)并進(jìn)行處理的,但是它也是有額外負(fù)擔(dān)的。

你可以想象一下這個(gè)過程:每當(dāng)一個(gè)事務(wù)被鎖的時(shí)候,就要看看它所依賴的線程有沒有被別人鎖住,如此循環(huán),最后判斷是否出現(xiàn)了循環(huán)等待,也就是死鎖。

當(dāng)有熱點(diǎn)數(shù)據(jù)被并發(fā)更新的話,每個(gè)新來(lái)的被堵住的線程,都要判斷會(huì)不會(huì)由于自己的加入導(dǎo)致了死鎖,雖然最終檢測(cè)的結(jié)果是沒有死鎖,但是這期間要消耗大量的 CPU 資源。因此,你就會(huì)看到 CPU 利用率很高,但是每秒?yún)s執(zhí)行不了幾個(gè)事務(wù)。

熱點(diǎn)行更新的解決策略: 降低并發(fā)度 :

1. 拆行,一行拆多行,減少鎖的沖突,以賬戶為例,可以考慮放在多條記錄上,比如 10 個(gè)記錄,賬戶總額等于這 10 個(gè)記錄的值的總和。這樣每次要給賬戶加金額的時(shí)候,隨機(jī)選其中一條記錄來(lái)加。這樣每次沖突概率變成原來(lái)的 1/10,可以減少鎖等待個(gè)數(shù),也就減少了死鎖檢測(cè)的 CPU 消耗。但其實(shí)這類方案需要根據(jù)業(yè)務(wù)邏輯做詳細(xì)設(shè)計(jì)。如果賬戶余額可能會(huì)減少,比如退款邏輯,那么這時(shí)候就需要考慮當(dāng)一部分行記錄變成 0 的時(shí)候,代碼要有特殊處理。

?2. Server 層限流,限制同一時(shí)間進(jìn)入更新的線程數(shù) ,比如在應(yīng)用網(wǎng)關(guān)層限流,或是使用sentinel之類的組件

3. 關(guān)閉死鎖監(jiān)測(cè)(關(guān)閉的弊端是可能超時(shí)較多)

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