【京東技術雙十一】記一次線上問題引發的對 Mysql 鎖機制分析
最近雙十一開門紅期間組內出現了一次因 Mysql 死鎖導致的線上問題,當時從監控可以看到資料庫活躍連線數飆升,導致應用層資料庫連線池被打滿,後續所有請求都因獲取不到連線而失敗。
整體業務程式碼精簡邏輯如下:
@Transactionpublic void service(Integer id) { delete(id); insert(id);}
資料庫例項監控:
當時透過分析上游問題流量限流解決後,後續找時間又重新分析了下問題發生的根本原因,現將其總結如下:本篇文章會先對 Mysql 中的各種鎖進行分析,包括互斥鎖、間隙鎖和插入意向鎖,讓大家對各種鎖的使用場景有一個瞭解,然後在此基礎上再對本問題進行分析,希望大家未來再碰到相似場景時,能夠快速的定位問題。
本篇文章中所有實驗用到的建表語句:
create table `test` (
`id` int(11) NOT NULL,
`num` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `num` (`num`)
) ENGINE = InnoDB;
insert into
test
values
(10, 10),
(20, 20),
(30, 30),
(40, 40),
(50, 50);
2.1 Shared and Exclusive Locks
S 鎖之間不互斥,多個事務可以同時獲取一條記錄上的 S 鎖
X 鎖之間互斥,多個事務不能同時獲取同一條記錄上的 X 鎖
S 鎖和 X 鎖之間互斥,多個事務不能同時獲取同一條記錄上的 S 鎖和 X 鎖
當多個事務同時去 update 索引上同一條記錄時,都需要先獲取到該記錄上的 X 鎖,所謂的鎖也就是會在記憶體中生成一個資料結構來記錄當前的事務資訊、鎖型別和是否等待等資訊。下圖中就是 T1 和 T2 同時去更新 id = 30 的這行記錄,並且 T1 成功獲取到了鎖,其在記憶體中生成的鎖結構資訊欄位 is_wating 為 false,可以繼續執行事務的後續邏輯,而 T2 獲取鎖失敗,則生成的鎖結構資訊欄位 is_wating 為 true,阻塞等待 T1 上的鎖釋放。
圖2. T1 和 T2 同時去更新 id = 30 的這行記錄示意
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`trx id 10078 lock_mode X locks rec but not gapRecord lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 6; hex 00000000274f; asc 'O;; 2: len 7; hex b60000019d0110; asc ;;
2.2 Gap Locks
session A | session B | |
T1 | select num from test where num > 10 and num < 15 for update; (0 rows) | |
T2 | insert into test values(12, 12); | |
T3 | select num from test where num > 10 and num < 15 for update; (1 rows) |
在上面這個場景中,session A 分別在 T1、T3 時刻進行了兩次範圍查詢,session B 在 T2 時刻插入了一條該範圍內的資料,如果 session A 能在 T3 時刻查詢出 session B 插入的資料,就說明發生了幻讀。此時只使用互斥鎖是無法解決幻讀的,因為 num = 12 的記錄在資料庫中還不存在,不能給其加上互斥鎖來防止 T2 時刻 session B 的插入。
引入了間隙鎖之後,session A 在 T1 時刻會給 id = 20 記錄生成一個 Gap Locks,之後 session B 在 T2 時刻想要插入記錄時,需要先判斷待插入位置的後一條記錄上是否存在 Gap Locks,很明顯此時 id = 20 的記錄上已經存在了 Gap Locks,那麼session B 就需要在 id = 20 的記錄上生成一個插入意向鎖,並進入鎖等待。
圖3.引入了間隙鎖後
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before recRecord lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000001e; asc 30 ;; 1: len 6; hex 00000000969c; asc ;; 2: len 7; hex a60000011a0128; asc (;; 3: len 4; hex 8000001e; asc ;;
間隙鎖雖然解決了幻讀問題,但因每次都會鎖住一段間隙,大大降低了資料庫整體的併發度,且因間隙鎖和間隙鎖之間不互斥,不同事務可以同時對同一間隙加上 Gap Locks,這也往往是各種死鎖產生的源頭。
2.3 Next-Key Locks
在上面 Gap Locks 的例子中事務 1 加的就是 Next-Key Locks,即同時給 id = 20 的記錄加了 X 鎖和 Gap 鎖。
圖4.同時給 id = 20 的記錄加了 X 鎖和 Gap 鎖
在可重複讀隔離級別下,update 和 delete 操作預設都會給記錄新增 Next-Key Locks,Mysql 中 Next-Key Locks 的鎖日誌資訊為:lock_mode X。
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;
2.4 Insert Intention Locks
實驗模擬如下:
session 1 | session 2 | session 3 | |
T1 | begin; | ||
T2 | select * from test where id = 25 for update; | ||
T3 | insert into test values(26, 26); (blocked) | ||
T4 | insert into test values(26, 26); (blocked) |
對於語句 select * from test where id = 25 for update 因當前表中不存在該記錄,在可重複讀隔離級別下,為了避免幻讀,會給 (20, 30] 間隙加上 Gap Locks。
從鎖日誌可以看出 session 1 給記錄 30 新增了間隙鎖(lock_mode X locks gap before rec)。
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before recRecord lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000001e; asc 30 ;; 1: len 6; hex 00000000969c; asc ;; 2: len 7; hex a60000011a0128; asc (;; 3: len 4; hex 8000001e; asc ;;
當 session 2 插入記錄 26 時,會在 B+ 樹中先定位到待插入位置,再判斷插入位置的間隙是否存在 Gap Locks,也就是判斷待插入位置的後一記錄 id = 30 是否存在 Gap Locks,如果存在需要在該記錄上生成插入意向鎖等待。
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38850 lock_mode X locks gap before rec insert intention waitingRecord lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000001e; asc 30 ;; 1: len 6; hex 00000000969c; asc ;; 2: len 7; hex a60000011a0128; asc (;; 3: len 4; hex 8000001e; asc ;;
線上問題分析
在對 Mysql 中的各種鎖結構有了一個清晰的瞭解之後,回過頭來再看看前面的線上問題:
@Transactionpublic void service(Integer id) { delete(id); insert(id);}
傳入的引數 id 在原資料庫中不存在 傳入的引數 id 在原資料庫中存在
本次主要會針對 id 記錄在原資料庫中不存在進行分析
session 1 | session 2 | session 3 | |
T1 | delete from test where id = 15; | ||
T2 | delete from test where id = 15; | delete from test where id = 15; | |
T3 | insert into test values(15, 15); | ||
T4 | insert into test values(15, 15); | ||
T5 | insert into test values(15, 15); |
因 id = 15 在資料庫中不存在,在 T1 時刻 session 1 會給其所在間隙的下一條記錄新增上 Gap Locks,又因 Gap Locks 不互斥, 在 T2 時刻 session 2 和session 3 都會同時獲取到 id = 20 的 Gap 鎖。
下圖中 tx: T1、T2、T3 分別代表 session 1、session 2 和 session 3。
圖6.
當在 T3 時刻 session 1 插入 id = 15 的記錄時,會判斷其插入位置的後一條記錄是否存在 Gap Locks,如果存在,則需要在該記錄上生成 Insert Intention Locks 並等待持有 Gap Locks 的事務釋放鎖。
圖7.
圖8.
在 T5 時刻 session 3 開始執行插入語句,此時同 T4 時刻,死鎖形成,session 1 生成的插入意向鎖正在等待 session 3 上的 Gap Locks 釋放,session 3 上生成的插入意向鎖正在等待 session 1 上的 Gap Locks 釋放,此時 session 3 回滾釋放所有鎖資源後,session 1 才可以最終執行成功。
圖9.
看看當時出現線上問題時,介面的呼叫量情況:
圖10.出現線上問題時介面的呼叫量
進一步在本地模擬 300 個執行緒併發執行,因人腦併發分析所有事務的執行情況的話會非常複雜,本次只以事務 1 為一個點來進行分析。
圖11.
因此對於未來在業務程式碼中存在相似邏輯的地方,一定要做好防重校驗,避免短時間記憶體在對同一行資料的先更新再插入的併發操作。同時在可重複讀隔離別下,更新和刪除操作預設都會新增 Next-Key Locks,間隙鎖的引入使得死鎖問題在併發情況下很容易出現,這也是在業務邏輯實現上需要考慮的問題。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2995377/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 記一次線上問題引發的對 Mysql 鎖機制分析MySql
- 【問題排查篇】一次業務問題對 ES 的 cardinality 原理探究 | 京東雲技術團隊
- 【京東技術雙十一】大資料平臺紅藍對抗——磨利刃,淬精兵!大資料
- 記一次線上mysql死鎖MySql
- 一次線上問題排查所引發的思考
- Mysql鎖機制分析MySql
- 一次 MySQL 線上死鎖分析實戰MySql
- 記一次線上問題 → 對 MySQL 的 ON UPDATE CURRENT_TIMESTAMP 的片面認知MySql
- 線上問題排查:記一次 Redis Cluster Pipeline 導致的死鎖問題Redis
- 京東AI走過雙十一:在技術時代建立信賴樣本AI
- MySQL學習日記(十一)-技術問答MySql
- 一次線上OOM問題分析OOM
- 記一次 MySQL select for update 死鎖問題MySql
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- MySQL 死鎖問題分析MySql
- 一次JSF上線問題引發的MsgPack深入理解,保證對你有收穫JS
- 記一次線上FGC問題排查GC
- 併發技術5:死鎖問題
- MySQL的事務機制和鎖(InnoDB引擎、MVCC多版本併發控制技術)MySqlMVC
- 全面瞭解mysql鎖機制(InnoDB)與問題排查MySql
- 記一次 Redisson 線上問題 → 你怎麼能釋放別人的鎖Redis
- 一次TiDB GC阻塞引發的效能問題分析TiDBGC
- MySQL鎖問題分析-全域性讀鎖MySql
- MySQL鎖等待與死鎖問題分析MySql
- 記錄一次spark連線mysql遇到的問題SparkMySql
- 線上公開課 | 京東雲監控系統設計及落地之路 京東雲技術新知
- mysql鎖機制 讀書筆記MySql筆記
- 【MySQL】MySQL中的鎖機制MySql
- 【併發技術04】執行緒技術之死鎖問題執行緒
- 教你用ActiveReports報表控制元件分析京東雙十一資料的價值控制元件
- mysql myisam的鎖機制MySql
- 記一次JVM FullGC引發嚴重線上事故的定位、分析、解決過程!JVMGC
- 記一次線上websocket返回400問題排查Web
- 記一次自定義starter引發的線上事故覆盤
- 線上併發事務死鎖問題排查
- MySql(三) MySql中的鎖機制MySql
- 線上BUG:MySQL死鎖分析實戰MySql
- 記一次線上崩潰問題的排查過程