记一次线上MySQL死锁问题排查与解决

出现问题
下午1点左右,我们开始陆续收到业务部门的反馈,说订单系统中有些订单状态卡在"处理中"无法继续流转。刚开始以为是偶发的网络问题,但投诉越来越多,我们立即开始排查。

开始排查
首先查看了订单服务的错误日志,发现大量数据库报错:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
查看Prometheus监控,发现MySQL的死锁计数在过去1小时内从平时的0-1次飙升到87次
SHOW PROCESSLIST看到有很多会话处于"updating"状态,持续时间都超过了5秒
SHOW ENGINE INNODB STATUS查看详细的死锁信息,发现了一个典型的死锁场景:

事务A的执行顺序:

  1. 先锁定了orders表的记录(order_id=10086)
  2. 然后尝试锁定order_items表的相关记录

事务B的执行顺序:

  1. 先锁定了order_items表的记录(order_id=10086)
  2. 然后尝试锁定orders表的记录
    这样就形成了循环等待,导致死锁。

开始查看代码:
订单服务(Java)的更新顺序是:先orders表,后order_items表
库存服务(Go)的更新顺序是:先order_items表,后orders表
在高并发时,这两个服务同时操作同一个订单的概率增大,导致死锁频发

紧急处理
对订单服务增加了事务重试机制(最多3次):

@Retryable(maxAttempts=3, backoff=@Backoff(delay=100))
public void updateOrderStatus() {
    // 业务逻辑
}

临时增加了数据库连接池大小,缓解阻塞

观察
修改上线后观察了24小时:
死锁次数降为0
订单状态更新成功率恢复到99.99%

常用SQL备忘
查看当前死锁信息

SHOW ENGINE INNODB STATUS\G

-- 查看锁等待情况
SELECT * FROM performance_schema.events_waits_current;

-- 查看当前运行的事务
SELECT * FROM information_schema.INNODB_TRX;

-- 查看锁信息
SELECT * FROM performance_schema.data_locks;

你可能感兴趣的:(javago)