1、数据库死锁示例

发生时间编号 事务A 事务B
1 BEGIN;
2 BEGIN;
3 SELECT * from role where id = 11 for update;
4 SELECT * from role where id = 12 for update;
5 SELECT * from role where id = 12 for update;
6 SELECT * from role where id = 11 for update;

事务 A 在等待事务 B 释放 id=12 的行锁,而事务 B 在等待事务 A 释放 id=11 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。

数据库返回信息:

  1. SELECT * from role where id = 11 for update
  2. > 1213 - Deadlock found when trying to get lock; try restarting transaction
  3. > 时间: 0s

2、死锁解决策略

  • 直接进入等待,直到超时(超时时间可以通过参数** innodb_lock_wait_timeout **来设置,默认50S)
  • 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行(参数 **innodb_deadlock_detect **设置为 on,表示开启死锁检测,数据库默认开启死锁检测)

正常情况下采用第二种策略,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。

主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。当并发量很高时,死锁的检测要消费大量的CPU资源。


热点行更新导致的性能问题
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。

怎么解决由这种热点行更新导致的性能问题呢?

1、如果能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。
2、控制并发度

  • 在客户端做并发控制
  • 中间件实现并发控制
  • 修改 MySQL 源码,对于相同行的更新,在进入引擎之前排队
  • 将一行改成逻辑上的多行来减少锁冲突