首页 > 技术文章 > 四、行锁(Mysql实战45讲笔记-基础)

zhaoyuan72 2021-04-13 15:32 原文

6. 行锁

 MySQL的行锁是引擎层各引擎自己实现的,不是所有引擎都支持行锁,MyISAM 就不支持。

行锁针对的数据表中行记录的锁,比如事务a更新某一行,事务b也要更新同一行,那必须等事务a执行完。

6.1 二阶段锁

锁的添加与释放分到两个阶段进行,之间不允许交叉加锁和释放锁。 也就是在事务开始执行后为涉及到的行按照需要加锁,但执行完不会马上释放,而是在事务结束时再统一释放他们。

下面的案例中,事务a中update两行数据,这个两行数据都被加锁,在事务a commit后,事务b才能执行。所以在事务中应当进行把会造成锁冲突、影响并发度的锁往后放。

image-20210406153526256

如下图,修改id=2的数据,会引起锁冲突,所以将修改id=2的数据放到最后,不影响事务中其他语句的执行,减少事务之间的锁等待,提升了并发度

执行顺序是:

  1. 事务a 修改id =1和id=2 的数据
  2. 事务b 修改id=3 的数据
  3. 事务a提交
  4. 事务b 修改id=2的数据,事务b 提交

6.2 死锁和死锁检测

死锁,事务a 等待事务b 释放id=2的行锁,事务b 等待事务a 释放id=1的行锁

6.3 出现死锁后的解决方法:

  • 直接进入等待,超时就放弃,事务回滚。innodb_lock_wait_timeout 来设置,InnoDB中默认值是50s,虽然可以修改等待时间,但是如果设置的过短,容易误伤正常的锁等待。
  • 发起死锁检测,发现死锁后,主动回滚死锁链条中某一个事务,让其他事务继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑,默认就是开启的,不过是有性能的消耗。

6.4 死锁检测的原理:

每当一个事务被锁,要查看该事务所依赖的所依赖的线程是有没被其他线程锁住,如此循环查看,判断是否出现死锁。

每个新来的被锁的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是O(n)的操作。

假设有1000个并发线程,同时更新一行,死锁的检测就是100万量级的,非常消耗cpu资源。

6.5 控制并发度决死锁检测消耗性能问题:

不要关闭死锁检测,关闭死锁,就需要等待超时,影响业务

通过控制并发度,比如同时最多只能有10个线程更新,那么死锁检测成本就比较低。

  • 客户端进行并发控制:若客户端很多,即使单个客户端并发线程控制了,所有的客户端的并发线程汇总到服务端,峰值还是很高。
  • 中间件实现:并发控制做在数据库服务端之前
  • 也可以直接修改mysql,在进入引擎前排队
  • 分段汇总,比如一个商城的总收入,用十行数据记录,总收入是这10条数据的总和,每次增加总额,任意加到10条数据中的某一条,锁冲突的概率是之前的十分之一,相当于子账户的概念

推荐阅读