Scott's world.

MySQL-行锁与死锁笔记

Word count: 1.1kReading time: 3 min
2019/11/05 Share

MySQL-行锁与死锁笔记

MySQL的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如MyISAM引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB是支持行锁的,这也是MyISAM被InnoDB替代的重要原因之一。

innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。

两阶段锁

我们先从这张图出发

KzheyV.png

实际上在事务A拿到id=1的行锁后在并没有事务提交的情况下,事务B需要id=1的行锁,因此在事务A还没有释放行锁的时候事务B处于阻塞的状态,所以在事务Acommit之后才会释放行锁

所以我们可以看出,在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

知道了这个设定,对我们使用事务有什么帮助呢?

那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

死锁和死锁检测

  • 死锁

    当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态

比如接下来这张图就是出现死锁的情况

KzhdTe.png

当出现了死锁之后,一般有两种策略

  • 通过参数innodb_lock_wait_timeout来设置超时时间,直接让事务进行等待,直到超时

    InooDB中默认innodb_lock_wait_timeout为50秒,对于在线服务来说,这个等待时间往往是无法接受的

  • 发起死锁检测,发现死锁后就主动回滚死锁链条中的某一个事务,让其他事务得以继续执行.将参数innodb_deadlock_detect设置为on即表示开启这个逻辑

    innodb_deadlock_detect的默认值本身就是on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。

但是当出现热点行更新导致的性能问题,通过以上两种策略不一定能很好地解决,一般有以下三种方法

  • 采取如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉,但是这种操作一般不建议具有较大风险

  • 控制并发度,且这个并发控制要做在数据库服务端

    如果你的团队有能修改MySQL源码的人,也可以做在MySQL里面。基本思路就是,对于相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。

  • 考虑通过将一行改成逻辑上的多行来减少锁冲突

问题

如果你要删除一个表里面的前10000行数据,有以下三种方法可以做到:

  • 第一种,直接执行delete from T limit 10000;
  • 第二种,在一个连接中循环执行20次 delete from T limit 500;
  • 第三种,在20个连接中同时执行delete from T limit 500。

你会选择哪一种方法呢?为什么呢?

回答

方案一,事务相对较长,则占用锁的时间较长,会导致其他客户端等待资源时间较长。
方案二,串行化执行,将相对长的事务分成多次相对短的事务,则每次事务占用锁的时间相对较短,其他客户端在等待相应资源的时间也较短。这样的操作,同时也意味着将资源分片使用(每次执行使用不同片段的资源),可以提高并发性。
方案三,人为自己制造锁竞争,加剧并发量。
方案二相对比较好,具体还要结合实际业务场景。

参考

https://time.geekbang.org/column/intro/139

CATALOG
  1. 1. MySQL-行锁与死锁笔记
    1. 1.1. 两阶段锁
    2. 1.2. 死锁和死锁检测
    3. 1.3. 问题
      1. 1.3.1. 回答
    4. 1.4. 参考