如何解决生产环境MySQL的死锁问题

1,511 阅读2分钟

生产问题

在生产环境中发现我们数据库出现了一个异常,异常堆栈信息如下:

Error updating database. 
 Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: 
Deadlock found when trying to get lock; try restarting transaction\n### The error may involve 
xxxMapper.updateByExampleSelective-Inline\n### The error occurred while setting parameters\n### 
SQL: UPDATE xxx  SET to_recipient_id = ?,logistics_order_id = ?,delivery_type = ?,delivery_no = ?,
delivery_company_code = ?,delivery_big_pen = ?,delivery_package_site_name = ?,delivery_extended_attribute = ?,
delivery_label_url = ?,customs_channel_code = ?,declare_no = ?,create_time = ?,update_time = ?,delete_flag = ?,
channel_hawb_code = ?,system_order_code = ?,change_sign = ?,delivery_extended_no = ?,delivery_child_no = ? 
WHERE (       (  logistics_order_id = ? ) )

从堆栈信息可以很容易知道死锁问题。但是这个更新语句为什么会出现死锁呢?

问题原因

死锁产生的原因有四个分别是:

  • 互斥
  • 循环等待
  • 不可剥夺
  • 请求与保持

只要产生死锁以上四个条件比然满足,因此考虑这个SQL语句是否产生了这四个死锁条件。

分析:

由于我们使用的是云数据库,因此可以通过云数据库控制台查看锁分析,分析结果如下:

可以看到死锁的产生是由于两个事务互相竞争导致的,那么两个事务如何产生死锁呢?

两个事务产生死锁的条件如下:

事务1: lock A, then B
事务2: lock B, then A

翻译一下就是:

事务1

update table 1 set name = 1 where id = 1;

update table2 set age = 2 where id = 3;

事务2

update table2 set age = 2 where id = 3;

update table 1 set name = 1 where id = 1;

即两个事务中,T1 锁定了A,要去获取B的资源锁,但是T2已经锁定了资源B,T2要去获取A的锁,两个都不释放,从而导致死锁。

根据这种场景分析生产执行SQL找到了对应的SQL问题,问题的原因也是前面描述的一样,两个事务互相竞争等待导致的。

解决方案

那么针对这种情况如何解决呢?

方案1:两个事务的执行SQL改成一样,即

事务1

update table 1 set name = 1 where id = 1;

update table2 set age = 2 where id = 3;

事务2

update table 1 set name = 1 where id = 1;

update table2 set age = 2 where id = 3;

按照相同的顺序执行SQL,即使出现并发情况,那么行锁也会等待而不会死锁。

方案2:提取事务,将不必要的SQL不加入事务中

事务1

update table 1 set name = 1 where id = 1;

commit;

update table2 set age = 2 where id = 3;

通过分析将不必要的SQL从事务中提取。