7 怎么减少行锁对性能的影响?
MySQL的行锁是在引擎层由各个引擎自己实现的,并不是所有的引擎都支持行锁,比如myisam引擎就不支持行锁,不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何 时刻只能有一个更新在执行,这就会影响到业务并发度。
这一章主要聊innodb的行锁,以及如何通过减少锁冲突来提升业务并发度。
从两阶段锁说起
Session a |
Session b |
begin; update t set k=k+1 where id=1; update t set k=k+1 where id=2; |
|
begin; update t set k=k+1 where id=1; |
|
commit; |
事务b的update语句在执行时候会是什么现象,假设id是t的pk
这个问题的结论取决于事务A在执行完两个update语句后,持有那些锁以及在什么时候释放,实际上事务b的update语句会阻塞,直到事务a的语句commit或者rollback,事务b才能够继续执行。
在innodb事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放,这个就是两阶段锁协议。
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
假设负责实现一个电影票在线交易业务,顾客A要在影院B购买电影票,简化一下,这个业务需要涉及到以下操作:
--1 从顾客A账户余额中扣除电影票价
--2 给影院B的账户余额增加这张电影票价
--3 记录一条交易日志。
也就说要完成这个交易,需要update两条记录并记录一个insert记录。当然,为了保证交易的原子性,我们需要把这三个操作放在一个事务中,那么,你会怎样安排这三个语句在事务中的顺序呢?
试想如果同时有另外一个顾客C要在影院B买票,那么这两个事务冲突的部分就是语句2了,因为它们要更新同一个影院账户的余额,需要修改同一行数据。
根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放,所以,如果把语句2放在最后,比如按照3,1,2这样的顺序,
那么影院账户余额这一行的锁时间就最少,这就最大程度地减少了事务之间的锁等待,提升了并发度。
现在由于正确的设计,影院余额这一行锁在一个事务中不会停留很长时间,但是这并没有完全解决。
如果在促销活动,大量的并发会导致mysql服务挂掉,这里说死锁和死锁检测
死锁和死锁检测
当并发系统中不同线程出现循环依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无线等待的状态,称为死锁
例
Session a |
Session b |
begin; update t set k=k+1 where id=1; |
begin; |
update t set k=k+1 where id=2; |
|
update t set k=k+1 where id=2; |
|
update t set k=k+1 where id=1; |
事务a在等待事务b释放id=2的行锁,而事务b在等待事务a释放id=1的行锁,互相等待就进入了死锁状态,出现了死锁状态,有两种策略:
--1 直接进入等待,直到超时,参数innodb_lock_wait_timeout
--2 发起死锁检测,主动回滚死锁链中的某一个事务,让其他事务继续执行,参数innodb_deadlock_detect=on,表示开启这个逻辑
在innodb中,innodb_lock_wait_timeout默认50s,如果采用这个策略,当出现死锁后,第一个被锁住的线程要50s才会出现超时,然后其他线程才可以继续执行,对于在线服务来说,这个等待时间是无法接受的。
但是这个innodb_lock_wait_timeout又不能设置很小的值,比如1s,出现了死锁是会很快解开,但是如果不是死锁呢,是锁等待呢,就会出现很多误伤。
所以正常情况采用策略2,主动监测死锁,参数innodb_deadlock_detect默认on。
但是如果并发度很高,所有事务都更新同一行,每个新来的被阻塞的线程都要判断会不会由于自己的加入导致了死锁,时间复杂度O(n)的操作,假设有1000个并发线程要同时更新同一行,
那么死锁检测操作就是100w这个量级的,虽然最终检测的结果是没有死锁,但是这会消耗大量的cpu资源。
另一个控制并发度,如果能够在客户端做并发控制,但是可能会由于客户端很多,也会有很高的并发,在数据库服务端做,如果有中间件,可以考虑在中间件实现,如果能修改mysql源码,
也可以在mysql里面做,基本思路就是相对于同行的更新,在进入引擎之前排队,这样在innodb内部就不会有大量的死锁检测工作了
但是,能不能从设计上来优化这个问题呢
可以考虑将一行改成逻辑上的多行来减少锁冲突,以影院账户为例,可以考虑放在多条记录上,比如10个记录,影院的账号总额等于这10个账户记录的值的总和,这样每次给影院账户修改金额的时候,
随机选其中一个记录来修改,这样每次冲突概率变为原来的1/10,可以减少锁等待个数,也就减少了死锁检测的cpu消耗。
这个方案需要根据业务逻辑做详细设计,如果有退款逻辑等。
思考题:
如果要删除一个表里的前10000行数据,以下三种方法:
--1 执行直接delete from t limit 10000;
--2 在一个连接中循环执行20次delete from t limit 500;
--3 在20个连接中同时执行delete from t limit 500;
原文地址:https://www.cnblogs.com/yhq1314/p/11103996.html