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

上一篇文章介绍了全局锁的特性,并且与其他功能相同的方法做了对比;讲解了表锁的两种类型,今天主要来说一下MySQL的行锁。

行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,MyISAM引擎就不支持行锁。不支持意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB是支持行锁的,这也是MyISAM被InnoDB替代的重要原因之一。

因此今天主要来围绕行锁,以及如何通过减少锁冲突来提升业务并发度。

行锁

行锁就是针对数据表中行记录的锁。这很好理解,比如事务A更新了一行,而这时候事务B也要更新同一行,则必须等事务A的操作完成之后才能进行更新。

当然,数据库中还有一些没那么一目了然的概念和设计,这些概念如果理解和使用不当,容易导致程序出现非预期行为,比如两阶段锁。

两阶段锁

假设事务B的update语句执行会是什么现象呢(id是表t的主键)
在这里插入图片描述
结论取决于事务A在执行完两条update语句后,持有哪些锁,以及在什么时候释放。实际上事务B的update语句会被阻塞,直到事务A执行commit之后,事务B才能继续执行。

因此事务A持有的两个记录的行锁,都是在commit的时候才释放的。

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

所以,如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

经典的事务问题举例:
假设要实现一个电影票在线交易业务,顾客A要在影院B购买电影票,这个业务需要涉及到以下操作:

  1. 从顾客A账户余额中扣除电影票价
  2. 给影院B的账户余额增加这张电影票价
  3. 记录一条交易日志

就是说要完成这个交易,需要update两条记录,并insert一条记录。为了 保证交易的原子性要把这三个操作放在一个事务中。那么如何安排这三个语句在事务中的顺序呢?

试想如果有一个顾客C也要在影院买票,那么两个事务冲突的部分即使语句2了,因为它们要更新同一行数据。

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

如果影院做活动:可以低价预售一年内所有的电影票,并且这个活动只做一天。那么服务器肯定会宕机,CPU消耗接近100%但是整个数据库每秒执行不到100个事务,这是什么原因呢?

因此这里要说一下死锁和死锁检测了。

死锁和死锁检测

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待状态,称为死锁。用数据库中的行锁举个例子:
在这里插入图片描述
事务A在等待事务B释放id=2的行锁,事务B在等待事务A释放id=1的行锁,就会进入死锁状态。出现死锁之后又两种策略:

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

在InnoDB中,innodb_lock_wait_timeout的默认值是50s,意味着如果采用第一个策略,出现死锁之后要等待50s后超时退出,对于在线服务器来说这个等待时间是无法接受的。

有人也许会说,将超时时间设置成一个很小的值,可以解决死锁;但是如果是简单的锁等待,就会出现“误伤队友”的情况

因此正常情况下我们还是要采用第二种策略。

主动死锁检测

每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是O(n)的操作。假设1000个并发线程要同时更新同一行,死锁检测操作就是100万这个量级的。虽然最终检测结果没有死锁,但是这期间要消耗大量的CPU资源。就会看到CPU利用率很高但是每秒却执行不了几个事务。

上面讲述的死锁检测要耗费大量的CPU资源,那么怎么解决由这种热点行更新导致的性能 问题呢?

  • 第一、头痛医头方法

如果确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。虽然有一定的风险,但是减少了一定的CPU使用率。
因为业务设计的时候出现死锁一般就会回滚然后重试就可以了,这是业务无损的。但是关掉死锁检测意味着可能会出现大量的超时,这是业务有损的。

  • 第二、控制并发度

根据上面的分析,发现如果并发能够控制住就能将死锁检测成本降低很多。而控制并发不可以做在客户端,因为即使每个客户端控制很少的并发线程,汇总到数据库服务端以后,峰值并发数也是成倍增长的。

因此并发控制要做在服务端。如果有中间件,可以考虑在中间件实现:如果你的团队有能修改MySQL源码的人, 也可以做在MySQL里面。 基本思路是:对于相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。

如何从设计上优化这个问题?

可以考虑通过将一行改成逻辑上的多行来减少锁冲突。在多用户对数据库某一行进行update的时候按照随机的方式去排队这要减少锁的个数,也就减少了CPU消耗,但是逻辑复杂。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值