一次詭異的數(shù)據(jù)庫(kù)“死鎖”,問(wèn)題究竟在哪里?
程序死鎖的問(wèn)題,很難調(diào)試,看進(jìn)程堆棧,看各個(gè)線程與鎖的情況,對(duì)照代碼進(jìn)行排查。
數(shù)據(jù)庫(kù)死鎖的問(wèn)題,更難,看不了數(shù)據(jù)庫(kù)堆棧,也看不了數(shù)據(jù)庫(kù)線程與鎖,更難以對(duì)照代碼排查。
前段時(shí)間,和一個(gè)朋友討論了一個(gè)“疑似”數(shù)據(jù)庫(kù)死鎖的問(wèn)題,最后進(jìn)行試驗(yàn)與排查,找到了問(wèn)題所在。
場(chǎng)景如下:
同一個(gè)表,高并發(fā)事務(wù),事務(wù)內(nèi)先插入一條記錄,再更新這條記錄:
- 如果更新的是唯一索引,有異常;
- 如果更新的是自增主鍵,就沒(méi)有異常;
畫(huà)外音:先不要被“dead lock”描述所迷惑,是死鎖問(wèn)題,阻塞問(wèn)題,還是其他異常,還另說(shuō)。
而且,據(jù)朋友所述,還能夠復(fù)現(xiàn):
- 開(kāi)啟事務(wù);
- 插入記錄;
- sleep 5秒;
- 修改被插入的記錄;
在并發(fā)時(shí)穩(wěn)定復(fù)現(xiàn)。
根據(jù)朋友的描述,在線下開(kāi)了多個(gè)MySQL客戶端進(jìn)行了并發(fā)模式測(cè)試,結(jié)果還挺出乎意料的。
第一步:數(shù)據(jù)準(zhǔn)備
- create table t (
- id int(20) primary key AUTO_INCREMENT,
- cell varchar(20) unique
- )engine=innodb;
新建表:
- 存儲(chǔ)引擎是innodb,MySQL版本是5.6;
- id字段,自增主鍵;
- cell字段,唯一索引;
- start transaction;
- insert into t(cell) values(11111111111);
- insert into t(cell) values(22222222222);
- insert into t(cell) values(33333333333);
- commit;
插入一些測(cè)試數(shù)據(jù)。
第二步:session參數(shù)設(shè)置
事務(wù)的隔離級(jí)別,事務(wù)的自動(dòng)提交等參數(shù)設(shè)置不當(dāng),都會(huì)對(duì)實(shí)驗(yàn)的結(jié)果產(chǎn)生影響,詢問(wèn)了朋友,事務(wù)的隔離級(jí)別是RR(repeatable read)。
- set session autocommit=0;
- set session transaction isolation level repeatable read;
每一個(gè)session啟動(dòng)后:
- 關(guān)閉自動(dòng)提交;
- 把事務(wù)隔離級(jí)別設(shè)為RR;
- show session variables like "autocommit";
- show session variables like "tx_isolation";
不放心的話,可以用上面兩個(gè)語(yǔ)句查詢確認(rèn)。
第三步:多個(gè)終端session模擬并發(fā)事務(wù)
如上圖,用SecureCRT開(kāi)啟兩個(gè)窗口:
- 窗口A,先啟動(dòng)事務(wù),并插入記錄;
- 窗口B,再啟動(dòng)事務(wù),也插入記錄;
- 窗口A,修改插入的記錄;
- 窗口B,也修改插入的記錄;
奇怪的現(xiàn)象發(fā)生了,如果并發(fā)事務(wù)的update語(yǔ)句:
- 更新條件是cell,就會(huì)發(fā)生異常;
- 更新條件是id,就一切正常;
按道理,插入不沖突的記錄,然后修改這條記錄,行鎖不應(yīng)該沖突呀?唯一索引,主鍵索引怎么會(huì)有差異呢?是否有關(guān)?是死鎖,還是其他原因?
大家?guī)兔Ψ治龇治?,到底?wèn)題在哪里呢?
【本文為51CTO專欄作者“58沈劍”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】