MySQL实战45讲——07|行锁功过:怎么减少行锁对性能的影响

文章探讨了MySQL中的行锁机制,特别是InnoDB引擎对行锁的支持,以及如何通过两阶段锁来管理并发事务。同时,文章提到了死锁的概念和处理方法,包括死锁检测与超时设置。面对热点行更新导致的性能问题,文章提出了通过控制并发度和改变数据结构以减少锁冲突的策略。
摘要由CSDN通过智能技术生成

07|行锁功过:怎么减少行锁对性能的影响

请支持正版:MySQL实战45讲

MySQL的行锁是在引擎层由各个引擎实现的,但并不是所有的引擎都支持行锁,比如MyISAM引擎不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响并发

InnoDB是支持行锁的,这也是MyISAM被InnoDB替代的原因之一

所以,接下来咱们聊的就是MySQL的行锁,以及减少行锁对并发的影响

行锁:针对数据表种行记录的锁,也就是说事务A更新了一行,而这时候事务B也要更新同一行,则必须等到事务A的操作完成后才能进行更新

两阶段锁

下面有一个例子,事务B的update语句执行时会是什么现象?假设字段id是表t的主键

事务A事务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 + 2 where id = 1;
commit;

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

事实上:事务B的update会被阻塞,直到事务A执行commit之后,事务B才能继续执行

也就是说事务A拥有两个记录的锁,都是在commit的时候释放的,那么结论就是:在InnoDB种,行锁是在需要的时候才加上的,但是并不是不需要的时候就立刻释放,而是要等到事务结束的时候才释放。这个就是两阶段锁

知道了这个有什么用呢,其实:如果你的事务需要锁住多个行,要把最可能造成冲突的行尽可能的放在最后

死锁和死锁检测

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

例如:

事务A事务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的行锁,事务A和事务B在互相等待对方的资源释放,就是进入了死锁状态。当出现死锁的时候,有两种策略:

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

在Innodb中innodb_lock_wait_timeout的默认值是50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要经过50s才会超时退出,然后其他线程才有可能继续执行。对于大型系统,这个时间是完全无法接受的

但是我们又不能把时间设置一个成一个很小的值,比如1s,这样当遇到死锁的时候,确实是很快就解开,但如果不是死锁,而是普通的锁等待呢?所以,如果超时时间设置的太短,那么会出现很多误伤

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

想象一下:当一个事务锁被锁的时候,就要看看它依赖的线程有没有被被人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁

那如果是我们所有事务都要更新同一行呢?

每个新来的被堵住的线程,都要判断会不会由于自己的加入是否导致了死锁,这是一个时间复杂度是O(n)的操作

假设有1000个线程并发更新同一行,那么死锁检测就是100万的量级。虽然最终的结果是没有死锁,但是这期间要消耗大量的cpu资源,因此你会看到cpu资源利用率很高,但是每秒却执行不了几个事务

根据上面的分析,我们接着讨论:怎么解决这种热点行更新导致的性能问题

  1. 如果你能确保这个业务不会出现死锁,那你可以把死锁检测关了。但是这么做是有风险的。是实际业务中,出现死锁就进行回滚,这个是业务无损的,而关闭死锁检测出现大量超时,这个是业务有损的
  2. 控制并发度。如果并发量可以控制住,比如说同一行最多只有10个线程在更新,那么死锁检测的成本很低,就不会出现这个问题。所以一个很直接的想法就是在客户端做并发控制,但是这个办法很明显的不好,因为客户端需要有很多,尤其是项目开发时期,这个是无法避免的。因此,并发控制只能在MySQL的服务端,如果有中间件,也可以在中间件实现。

基本思路是:对于相同的行的更新,在进入引擎之前进行排队,这样InnoDB就不会有大量的死锁检测

如何实现这个方案呢?

可以考虑通过把一行改成逻辑上的多行来避免锁冲突,比如说,要更新某账户余额,可以把余额放在多条记录上,比如10条记录,总额度等于这10个记录的值的总和。每次要加金额的时候,随机加其中一条即可,这样冲突概率就大大减少

但是这样做还有一些细节需要处理,比如说余额为0的时候

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值