四、行锁(Mysql实战45讲笔记-基础)
6. 行锁
MySQL的行锁是引擎层各引擎自己实现的,不是所有引擎都支持行锁,MyISAM 就不支持。
行锁针对的数据表中行记录的锁,比如事务a更新某一行,事务b也要更新同一行,那必须等事务a执行完。
6.1 二阶段锁
锁的添加与释放分到两个阶段进行,之间不允许交叉加锁和释放锁。 也就是在事务开始执行后为涉及到的行按照需要加锁,但执行完不会马上释放,而是在事务结束时再统一释放他们。
下面的案例中,事务a中update两行数据,这个两行数据都被加锁,在事务a commit后,事务b才能执行。所以在事务中应当进行把会造成锁冲突、影响并发度的锁往后放。
如下图,修改id=2的数据,会引起锁冲突,所以将修改id=2的数据放到最后,不影响事务中其他语句的执行,减少事务之间的锁等待,提升了并发度
执行顺序是:
- 事务a 修改id =1和id=2 的数据
- 事务b 修改id=3 的数据
- 事务a提交
- 事务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条数据中的某一条,锁冲突的概率是之前的十分之一,相当于子账户的概念