MySQL死鎖排查與預(yù)防實(shí)戰(zhàn)
前言
線上日志里突然出現(xiàn)大量這個(gè)錯(cuò)誤:
Deadlock found when trying to get lock; try restarting transaction
死鎖是MySQL高并發(fā)場(chǎng)景下的常見問題。偶爾一兩次可以通過業(yè)務(wù)重試解決,但如果頻繁出現(xiàn),就需要從根本上排查和優(yōu)化。
這篇整理MySQL死鎖的排查方法和預(yù)防策略。
一、查看死鎖信息
MySQL有個(gè)命令能看到最近一次死鎖的詳情:
SHOW ENGINE INNODB STATUS\G
輸出很長,找LATEST DETECTED DEADLOCK這部分:
*** (1) TRANSACTION: UPDATE orders SET status = 'paid' WHERE id = 1001 *** (1) HOLDS THE LOCK(S): -- 持有orders表的鎖 *** (1) WAITING FOR THIS LOCK: -- 等inventory表的鎖 *** (2) TRANSACTION: UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 2001 *** (2) HOLDS THE LOCK(S): -- 持有inventory表的鎖 *** (2) WAITING FOR THIS LOCK: -- 等orders表的鎖 *** WE ROLL BACK TRANSACTION (2)
經(jīng)典的死鎖場(chǎng)景:事務(wù)A鎖了orders等inventory,事務(wù)B鎖了inventory等orders,互相等。
二、分析死鎖原因
知道是哪兩個(gè)SQL了,回去翻代碼。
原來下單邏輯里有兩種調(diào)用順序:
// 路徑A:先改訂單再扣庫存 updateOrderStatus(orderId, "paid"); decreaseInventory(productId, 1); // 路徑B:先扣庫存再改訂單(另一個(gè)接口) decreaseInventory(productId, 1); updateOrderStatus(orderId, "paid");
兩個(gè)接口都在事務(wù)里,剛好并發(fā)了就死鎖。
三、解決方案
最直接的辦法:統(tǒng)一加鎖順序。
不管哪個(gè)接口,都先操作orders再操作inventory(或者反過來,總之要一致)。
// 統(tǒng)一順序:先orders后inventory
@Transactional
public void processOrder(long orderId, long productId) {
updateOrderStatus(orderId, "paid"); // 永遠(yuǎn)先鎖orders
decreaseInventory(productId, 1); // 再鎖inventory
}
如果涉及多條記錄,按ID排序:
List<Long> ids = Arrays.asList(id1, id2, id3);
Collections.sort(ids);
for (Long id : ids) {
lockAndProcess(id);
}
四、間隙鎖導(dǎo)致的死鎖
還有一種更詭異的死鎖,兩個(gè)事務(wù)操作的都不是同一行數(shù)據(jù)。
這通常是間隙鎖的問題。RR隔離級(jí)別下,SELECT ... FOR UPDATE如果沒命中數(shù)據(jù),會(huì)鎖一個(gè)"間隙"。
比如user_id有1、5、10三條記錄:
-- 事務(wù)A SELECT * FROM orders WHERE user_id = 3 FOR UPDATE; -- 沒有user_id=3的數(shù)據(jù),但會(huì)鎖住(1,5)這個(gè)間隙 -- 事務(wù)B SELECT * FROM orders WHERE user_id = 7 FOR UPDATE; -- 鎖住(5,10)這個(gè)間隙 -- 然后兩邊各自INSERT -- 事務(wù)A想插入user_id=6,要等(5,10)的間隙鎖 -- 事務(wù)B想插入user_id=4,要等(1,5)的間隙鎖 -- 死鎖
解決辦法:
- 改用RC隔離級(jí)別(間隙鎖少很多,但要注意幻讀)
- 用唯一索引精確查詢,避免范圍鎖
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
五、縮小事務(wù)范圍
還有個(gè)常見問題是事務(wù)太長。事務(wù)越長,持有鎖的時(shí)間越久,死鎖概率越高。
// 這種寫法不好
@Transactional
public void process() {
queryData(); // 查數(shù)據(jù)
callExternalApi(); // 調(diào)外部接口,可能很慢
updateDatabase(); // 更新數(shù)據(jù)庫
}
// 改成這樣
public void process() {
queryData();
callExternalApi(); // 外部調(diào)用放事務(wù)外面
updateInTransaction();
}
@Transactional
public void updateInTransaction() {
updateDatabase(); // 只有真正需要事務(wù)的操作
}
六、監(jiān)控與告警
建議加上監(jiān)控:
# 簡單腳本,每分鐘檢查死鎖次數(shù)
DEADLOCKS=$(mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks'" | awk 'NR==2{print $2}')
echo "$(date) deadlocks: $DEADLOCKS" >> /var/log/deadlock.log
配合Prometheus的話:
- alert: MySQLDeadlock expr: increase(mysql_global_status_innodb_deadlocks[5m]) > 0 for: 1m
死鎖次數(shù)漲了就告警,別等業(yè)務(wù)反饋才知道。
七、業(yè)務(wù)層重試
有些場(chǎng)景死鎖確實(shí)很難完全避免,那就在業(yè)務(wù)層做重試:
int retry = 3;
while (retry-- > 0) {
try {
doTransaction();
break;
} catch (DeadlockException e) {
if (retry == 0) throw e;
Thread.sleep(100); // 等一下再試
}
}
MySQL檢測(cè)到死鎖會(huì)立即回滾一個(gè)事務(wù),不會(huì)一直卡著,所以重試通常能成功。
總結(jié)
死鎖本質(zhì)是資源競(jìng)爭問題,預(yù)防比解決更重要:
| 方法 | 效果 |
|---|---|
| 統(tǒng)一加鎖順序 | 最有效,從根本上避免死鎖 |
| 縮小事務(wù)范圍 | 減少鎖持有時(shí)間 |
| 合理使用索引 | 減少鎖的范圍 |
| 降低隔離級(jí)別 | 減少間隙鎖(RC級(jí)別) |
| 業(yè)務(wù)層重試 | 兜底方案 |
記住兩點(diǎn):統(tǒng)一加鎖順序、縮小事務(wù)范圍,能解決大部分死鎖問題。
到此這篇關(guān)于MySQL死鎖排查與預(yù)防實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)MySQL死鎖排查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
允許遠(yuǎn)程用戶訪問mysql服務(wù)sql語句
本節(jié)主要介紹了如何允許遠(yuǎn)程用戶訪問mysql服務(wù),本例授權(quán)192.168.14.1 主機(jī)的cakephp用戶訪問cakephp數(shù)據(jù)庫2014-07-07
MySQL高可用解決方案MMM(mysql多主復(fù)制管理器)
MySQL本身沒有提供replication failover的解決方案,通過MMM方案能實(shí)現(xiàn)服務(wù)器的故障轉(zhuǎn)移,從而實(shí)現(xiàn)mysql的高可用。MMM不僅能提供浮動(dòng)IP的功能,如果當(dāng)前的主服務(wù)器掛掉后,會(huì)將你后端的從服務(wù)器自動(dòng)轉(zhuǎn)向新的主服務(wù)器進(jìn)行同步復(fù)制,不用手工更改同步配置2017-09-09
MySQL性能優(yōu)化之table_cache配置參數(shù)淺析
這篇文章主要介紹了MySQL性能優(yōu)化之table_cache配置參數(shù)淺析,本文介紹了它的緩存機(jī)制、參數(shù)優(yōu)化及清空緩存的命令等,需要的朋友可以參考下2014-07-07
MySQL8.4一主一從環(huán)境搭建實(shí)現(xiàn)
本文主要介紹了MySQL8.4一主一從環(huán)境搭建實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
淺析如何保證MySQL與Redis數(shù)據(jù)一致性
在互聯(lián)網(wǎng)應(yīng)用中,MySQL作為持久化存儲(chǔ)引擎,Redis作為高性能緩存層,兩者的組合能有效提升系統(tǒng)性能,下面我們來看看如何保證兩者的數(shù)據(jù)一致性吧2025-06-06

