淺析MySQL二段鎖

背景

在介紹MySQL二段鎖以前,我須要理清一下概念,即MySQL二階段加鎖與二階段提交的區別:數據庫

二階段加鎖:用於單機事務中的一致性和隔離性
二階段提交:用於分佈式事務

何爲二段鎖

在一個事務操做中,分爲加鎖階段解鎖階段,且全部的加鎖操做在解鎖操做以前,具體以下圖所示:segmentfault

alt text

加鎖時機

當對記錄進行更新操做或者select for update(X鎖)、lock in share mode(S鎖)時,會對記錄進行加鎖,鎖的種類不少,不在此贅述。性能優化

什麼時候解鎖

在一個事務中,只有在commit或者rollback時,纔是解鎖階段。網絡

二階段加鎖最佳實踐

下面舉個具體的例子,來說述二段鎖對應用性能的影響,咱們舉個庫存扣減的例子:併發

方案一:
start transaction;
// 鎖定用戶帳戶表
select * from t_accout where acount_id=234 for update
//生成訂單
insert into t_trans;
// 減庫存
update t_inventory set num=num-3 where id=${id} and num>=3;
commit;
方案二:
start transaction;
// 減庫存
update t_inventory set num=num-3 where id=${id} and num>=3;
// 鎖定用戶帳戶表
select * from t_accout where acount_id=234 for update
//生成訂單
insert into t_trans;
commit;

咱們的應用經過JDBC操做數據庫時,底層本質上仍是走TCP進行通訊,MySQL協議是一種停-等式協議(和http協議相似,每發送完一個分組就中止發送,等待對方的確認,在收到確認後再發送下一個分組),既然經過網絡進行通訊,就必然會有延遲,兩種方案的網絡通訊時序圖以下:分佈式

alt text

因爲商品庫存每每是最致命的熱點,是整個服務的熱點。若是採用第一種方案的話,TPS理論上能夠提高3rt/rt=3倍。而這是在一個事務中只有3條SQL的狀況,理論上多一條SQL就多一個rt時間。性能

另外,當更新操做到達數據庫的那個點,纔算加鎖成功。commit到達數據庫的時候纔算解鎖成功。因此,更新操做的前半個rtcommit操做的後半個rt都不計算在整個鎖庫存的時間內。優化

性能優化

從上面的例子能夠看出,在一個事務操做中,將對最熱點記錄的操做放到事務的最後面,這樣能夠顯著地提升服務的吞吐量spa

select for update 和 update where的最優選擇

咱們能夠將一些簡單的判斷邏輯寫到update操做的謂詞裏面,這樣能夠減小加鎖的時間,以下:code

方案一:
start transaction
num = select count from t_inventory where id=234 for update
if count >= 3:
    update t_inventory set num=num-3 where id=234
    commit 
else:
    rollback

方案二:

start transaction:
    int affectedRows = update t_inventory set num=num-3 where id=234 and num>=3
    if affectedRows > 0:
        commit
    else:
        rollback

延時圖以下:
alt text

從上圖能夠看出,加了update謂詞之後,一個事務少了1rt的鎖記錄時間(update謂詞和select for update對記錄加的都是X鎖,因此效果是同樣的)。

死鎖

加鎖SQL都或多或少會遇到這個問題。上面的最佳實踐中,筆者建議在一個事務中,對記錄的加鎖按照記錄的熱點程度升序排列,對與任何會併發的SQL都必須按照相同的順序來處理,不然會致使死鎖,以下圖:

alt text

總結

合理地寫好SQL,對於咱們提升系統的吞吐量相當重要。

原文連接

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索