《MySQL行锁的利弊剖析:如何降低行锁对性能的消极影响》
行锁是针对数据表中行记录实施的锁定机制,由引擎层的相关引擎予以实现。
从两阶段锁说起
在InnoDB的事务场景下,行锁并非在需要时立即获取后就马上释放,而是要等到事务结束才会释放,此即为两阶段锁协议。
明确这一设定后,若事务需锁定多个行,需将最易引发锁冲突、最可能影响并发程度的锁尽量后置。
举个例子,存在电影票交易业务,顾客A要在影院B购买电影票,涉及如下操作:
-
从顾客A的账户余额中扣减电影票的费用;
-
向影院B的账户余额中增加电影票的费用;
-
记录一条交易的日志。
这三个操作对应三条语句,为保证原子性,会将其置于同一个事务中。观察可知,语句2最易产生冲突,因为不同顾客买票时都会通过语句2修改同一行数据。
依据两阶段锁协议,锁等待难以避免,而将语句2安排在最后,例如按312的顺序执行,就能让最易引发锁冲突的锁处于后面位置,最大程度减少事务间的等待情况。
死锁与死锁检测
当并发系统中不同线程出现循环的资源依赖情形,相关线程都在等待其他线程释放资源时,会致使这些线程陷入无限等待状态,此状况被称为死锁。
以行锁为例:
此时,事务A等待事务B释放id=2的行锁,事务B等待事务A释放id=1的行锁,从而造成死锁。
出现死锁时,有两种应对策略:
-
直接进入等待状态,直至超时。该超时时间可通过参数
innodb_lock_wait_timeout
进行设置。在InnoDB中,默认值为50秒,这通常难以接受。但也不能将该时间设置得过小,否则无法区分死锁与简单锁等待情况,易造成误判。所以,此方法一般不被采用。 -
发起死锁检测,发现死锁后,主动回滚死锁链条中的某一事务,使其他事务能够继续执行。设置参数
innode_deadlock_detect=on
,表示开启该逻辑。
正常情况下采用第二种策略,但死锁检测也有一定负担。若n个事务都要更新同一行,由于每个新被阻塞的线程都需判断自身是否引发死锁,其复杂度为O(n)
,总复杂度会达到O(n²)
。
针对这种热点行更新导致的性能问题,主要思路如下:
-
对于相同行的更新操作,在进入引擎前进行排队。如此,InnoDB内部就不会有大量死锁检测工作。
-
将一行数据拆分为逻辑上的多行以减少锁冲突。比如将影院账户拆成10个记录,每次增加金额时随机选取其中一条记录进行操作,每次冲突概率变为原来的1/10,可减少锁等待次数,进而降低死锁检测的CPU消耗。
文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/13063.html