如何减小行锁对性能的影响 – Jrient
mysql的行锁是在引擎层由各个引擎自己实现的,但并不是所有的引擎都支持行锁。
比如MyISAM就不支持行锁。不支持行锁就意味着并发控制只能使用表锁,这就会影响并发处理的效率。
两阶段锁
在Innodb事务中,行锁是在需要的时候才加上的,在事务结束的时候才会去释放。这就是两阶段锁协议。
因此,如果你的事务中需要锁多个行,要把最可能赵成锁冲突、最有可能影响并发度的锁尽量往后放。
死锁和检测
当并发系统中不同线程出现循环资源依赖,涉及的线程都在相互等待别的线程释放资源时,就会导致这几个线程进入无限等待的状态,称之为死锁。
当出现死锁之后,我们有两种策略进行处理:
1. 直接进入等待状态,知道超时。超时时间可以通过参数 innodb_lock_wait_timeout
来设置
2. 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一事务,让其他事务得意继续执行。设置参数 innodb_deadlock_detect
设置为 on
,来开启这个功能。
在innodb中, innodb_lock_wait_timeout
的默认值是50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过50s才会超时退出,其他线程才有可能继续执行。
对于这么长的等待时间,对业务来说往往是不能接受的,但是将等待时间设置过短,又会导致误伤正常的所等待。因此我们还是尽量采用第二种策略。
默认 innodb_deadlock_detect
是 on
。主动死锁检测在发生死锁的时候是可以快速的发现并处理,但是它也会有额外的负担。一旦被死锁的行是热点行,对于时间复杂度是O(n)的死锁检测来说,将会小号大量的cpu资源。
因此我们就需要讨论如何解决由于这种热点行更新导致的性能问题。
一种方案是,如果能确保业务一定不会出现死锁,那就可以临时把死锁检测关掉。但是这种操作本身带有一定的风险,一旦出现死锁就会带来大量的请求超时,这是业务有损的。
另一种方案是控制并发度。比如同一行同时最多只有10个线程在更新,那么死锁检测的成本就很低。但是一旦用户量比较大的时候,是无法保证并发数的。因此可以考虑在数据库服务端,采用中间件的形式操作,对于相同行的更新,进入引擎之前进入队列排队。
还有一种方案是通过业务设计的角度来优化这个问题。可以考虑通过将一行改成逻辑上多行来减少行锁的冲突,更新操作随机命中其中一行,这样就可以减少死锁检测的CPU消耗。