mysql 死锁

死锁的发生

不同线程出现资源的循环依赖,都在等待对方释放自己所需要的资源,就会导致这几个线程进行无限等待的状态,发生死锁。

事务A 事务B
Begin; Begin;
update t set k=k+1 where id = 1;
update t set k=k+1 where id = 2;
update t set k=k+1 where id = 2;(block)
update t set k=k+1 where id = 1;

<ERROR 1213 (40001):
Deadlock found when trying to get lock;
try restarting transaction
Query OK, 1 row affected (6.99 sec)
Rows matched: 1 Changed: 1 Warnings: 0

事务A在等待事物B释放 id=2的行锁,而事务B在等待事物A释放id=1的行锁,双方都在等待对方释放资源,就发生了死锁。由于MySQL有死锁检测,会马上发现这个死锁,并对事务B进行回滚。

发生死锁的线程都是要锁至少2行(参与的有2个资源,一个资源是自己已经加锁,但别人也要加,另一个资源是别人已经加锁,但自己也要加)。如果一个事务只锁一行是不会发生死锁的,只会发生锁阻塞。

应对策略

  • 什么都不做,直接等到超时

    上面的事务B,会发生超时。

    通过设置innodb_lock_wait_timeout来指定超时时间,默认值是50s

    show variables like '%innodb_lock_wait_timeout%';
    +--------------------------+-------+
    | Variable_name            | Value |
    +--------------------------+-------+
    | innodb_lock_wait_timeout | 50    |
    +--------------------------+-------+
  • 进行死锁检测

    开启死锁检测功能,检测到死锁后,对回滚成本比较低的事务进行回滚,让其它事务继续执行。设置参数innodb_deadlock_detect为on,开启此功能(默认为开启)

    +------------------------+-------+
    | Variable_name          | Value |
    +------------------------+-------+
    | innodb_deadlock_detect | ON    |
    +------------------------+-------+

哪种策略更好

  • 缩短等待的超时时间

    innodb_lock_wait_timeout的默认等待为50秒,对于生产环境,这显然是无法接受的。如果设置为1秒呢,虽然等待的时间变短,但也会误伤那些只是等待锁,而不是陷入死锁的线程。比如2秒以后就可以拿到锁的那些线程。

  • 启用死锁检测

    MySql默认启用死锁检测,当发现加入进来的线程会产生死锁时,会回滚成本较低的事务。MySQL发现死锁的速度很快,所以推荐使用死锁检测

  • 关闭死锁检测

    如果可以确定所有的SQL不会产生死锁问题,可以关闭死锁检测。死锁检测虽然好使,但也是有代价的,会占用CPU的资源。

死锁检测的成本

当一个线程新加入到某个资源的阻塞队列时,会检测它的加入是否与其它正在发生阻塞的线程存在资源的相互依赖,从而导致死锁的发生。如果这是一个高并发的资源,阻塞队列里有大量排队的线程,那么每个线程都要把其它线程检查一遍,每个线程要检查的时间复杂度就是O(N)

比如有1000个并发线程,那么要总共要检测的数量就是 1000 * 1000 = 100W,即O(N^2),这种数量级的检测就会导致消耗大量的CPU资源,你看到的现象就是CPU占用率很高,却处理不了多少事务,或是你发现理处的事务很少,但CPU占用率却很高。

控制并发度

要想从根本上减少死锁及锁等待,就要降低对同一资源的并发访问数量

可以使用的方法

  • 分摊热点资源的访问量

    比如参加秒杀的商品,它的库存如果存放在一条记录中,那么在高并发下,比如有1000个请求,就会同时更新,这样就会导致线程的阻塞或发生死锁。

    可以把保存库存的记录一条拆成N条,让请求随机访问这N条记录,比如分别放在100个记录中,那么每个记录最多只有10个更新请求,这样可以把并发量降为原来的1/N,大大减小了死锁的发生和锁的等待, 以及死锁检测的成本

  • 把并发请求放入队列

    数据库中间件可以把请求放入队列,使并发请求变为顺序访问