在介紹MySQL二段鎖
以前,我須要理清一下概念,即MySQL
二階段加鎖與二階段提交的區別:數據庫
二階段加鎖:用於單機事務中的一致性和隔離性 二階段提交:用於分佈式事務
在一個事務操做中,分爲加鎖階段
和解鎖階段
,且全部的加鎖操做在解鎖操做以前,具體以下圖所示:segmentfault
當對記錄進行更新操做或者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
協議相似,每發送完一個分組就中止發送,等待對方的確認,在收到確認後再發送下一個分組),既然經過網絡進行通訊,就必然會有延遲,兩種方案的網絡通訊時序圖以下:分佈式
因爲商品庫存每每是最致命的熱點,是整個服務的熱點。若是採用第一種方案的話,TPS
理論上能夠提高3rt/rt=3
倍。而這是在一個事務中只有3條SQL的狀況,理論上多一條SQL就多一個rt時間。性能
另外,當更新操做到達數據庫的那個點,纔算加鎖成功。commit
到達數據庫的時候纔算解鎖成功。因此,更新操做的前半個rt
和commit
操做的後半個rt
都不計算在整個鎖庫存的時間內。優化
從上面的例子能夠看出,在一個事務操做中,將對最熱點記錄的操做放到事務的最後面,這樣能夠顯著地提升服務的吞吐量
。spa
咱們能夠將一些簡單的判斷邏輯寫到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
延時圖以下:
從上圖能夠看出,加了update謂詞之後,一個事務少了1rt的鎖記錄時間(update謂詞和select for update對記錄加的都是X鎖,因此效果是同樣的)。
加鎖SQL都或多或少會遇到這個問題。上面的最佳實踐中,筆者建議在一個事務中,對記錄的加鎖按照記錄的熱點程度升序排列,對與任何會併發的SQL都必須按照相同的順序來處理,不然會致使死鎖,以下圖:
合理地寫好SQL,對於咱們提升系統的吞吐量相當重要。