行级锁对性能的影响

怎样减少行级锁对性能的影响

MySQL的行级锁是在引擎层有各个引擎自己实现的。但并不是所有的引擎都支持行级锁,比如MyISAM引擎就不支持行级锁。

不支持行级锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上的任何时刻只能有一个更新在执行,这就会影响到业务的并发度,InnoDB是支持行锁的。这也是InnoDB用来代替MyISAM的原因之一。

从两阶段锁说起

在下面的操作序列中,事务B的update语句执行时会是什么现象呢?假设字段id是表t的主键。

在这里插入图片描述
这个问题的结论取决于事务A在执行完两条update语句后,持有哪些锁,以及在什么时候释放。

实际上事务B的update语句会被阻塞,直到事务A执行commit之后,事务才会继续执行。

也就是说,在InnoDB引擎中,行锁是在需要的时候才加上的,但并不是不需要了就立即释放,而是要等到事务结束时才释放。这个就是两阶段锁协议

这个有什么用呢?
如果你的事务中需要锁多个行,要把最可能造成的锁冲突、最可能影响并发度的锁尽量往后放。

假设顾客A要在影院B购买电影票。

  1. 从顾客A账户余额中扣除电影票的票价
  2. 给影院B的账户余额增加上去
  3. 记录一条交易日志
    (相当于转账)
    从上面的分析来看我们需要两条update语句和一条insert语句,当然为了保证原子性操作,我们需要把他们都放在一个事务中。那么要怎样安排SQL执行的顺序才合适呢?

根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才是释放的。所以,如果你把语句update 安排在最后,比如3,1,2这样的顺序,那么修改影院账户余额持有行锁的时间就最少。这就是最大程度的减少了事务之间的锁等待,提升了并发度。

死锁和死锁检测

当并发系统中不同线程出现对各自资源的循环依赖,涉及的线程都在等待对方线程释放资源时,就会导致这几个线程都进入无线等待的状态,这个现象称为死锁。

在这里插入图片描述

这个时候,事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。事务A和事务B在互相等待对方的资源释放,就是进入了死锁状态。当出现死锁以后,有两种策略。

  • 一种策略是直接进入等待,直接超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。
  • 另一种策略是:发起死锁检测,发起死锁后,主动回滚死锁链条中的某一个事务,让其他事务可以执行。将参数innodb_deadlock_delete设置为on,表示开启这个逻辑。

在InnoDB中, innodb_lock_wait_timeout的默认值是50s,意味着如果此阿勇第一个策略,当出现死锁以后,第一个被锁住的线程要过50s才会超时退出,然后其他线程才有可能继续执行。对于这个等待时间是无法接受的。

但是我们也不能将这个时间设置成1s。这样当出现死锁的时候确实很快就能解开。

但如果不是死锁而是简单的锁等待呢?等待时间是1s,那就失去了等待的意义。

所以正常情况下我们还是要采用第二种策略,即主动死锁检测,而且innodb_deadlock_detect的默认值本身就是on 。主动死锁检测在发生死锁的时候就是能够快速发现并进行处理的,但是也有额外的负担

负担来自CPU的消耗,每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待。如果所有的事务都作用与同一行,那么都会判断自己的加入会不会造成循环依赖;时间复杂度是O(n),假设检测的线程时百万量级的,那么检测期间就会消耗大量的CPU资源。造成的现象是CPU利用率很高,但是每秒却执行不了几个事务。

那么这种现象该如何解决呢?

  1. 关掉死锁检测。但是这样做会出现一定的风险,有可能对业务产生重大影响
  2. 控制并发度。如果并发量能控制那么检测的成本就很低的,可以在客户端进行并发控制或者使用中间件。基本思路就是对于相同行的更新,在进入引擎之前排队。这样InnoDB内部就不会有大量的死锁检测工作了。

总结:就是在一些业务场景下,例如秒杀活动,我们需要提高系统的并发度让秒杀过程更快的执行完成,优化的措施可以根据两阶段锁协议,调整SQL执行的顺序,优化行级锁使整个过程执行的时间更短。

猜你喜欢

转载自blog.csdn.net/qq_43672652/article/details/106159176