MySQL的行锁是由各个引擎自己实现的,但不是所有引擎都支持,例如MyISAM就不支持行锁,在处理并发性问题的时候就只能使用表锁的方式。InnoDB是支持行锁,这也是它取代MyISAM引擎的原因之一。
1.1两阶段锁协议
例有如下事务发生,事务A在执行过程中会优先持有id=1和id=2的行锁,等待事务提交的时候才会将持有的行锁释放,事务B才能继续执行,这就是两阶段锁协议。
两阶段锁协议: 在事务执行中,会因需请求行锁,请求到锁继续执行,请求不到则阻塞挂起,直到事务提交,再将持有的行锁释放出去。
知道上述设定,我们可以拿来干嘛呢?例如有如下业务:用户A向商家B购买物品,那我们可以想到的实现流程如下:
- 用户A的持有金额减一
- 商家B收入金额加一
- 记录一条交易日志
现在如果存在大量用户并发购物,受影响的会是"操作2"对应的行锁,我们将三个操作放入一个事务中,将受影响最大的,也就是“操作2”放到事务最后,将“操作2”在事务中的持有时间,例如操作从3→1→2顺序放。
1.2死锁和死锁检测
死锁产生的条件,用Java的话说需要如下四个条件:
- 对已持有的资源不释放,并持续请求其他资源
- 资源为独占资源
- 已持有的资源只能释放,不能强行剥夺
- 环形等待条件
举例,如下图:
事务A持有id=1的行锁,请求id=2的行锁;事务B持有id=2的行锁,请求id=1的行锁,完美满足上述死锁的生成条件,想要避免死锁,打破其中一条就行了,如果避免死锁或者已经出现了死锁,如何打破?
策略有很多,例如将资源设为共享,规定资源的获取顺序,设置请求资源的超时时间,死锁检测机制等等。
MySQL对后两种策略有着支持:
- 设置请求资源的超时时间:可以用过参数innodb_lock_waitout来设置,默认为50s
- 死锁检测机制:但发现有死锁发生,主动回滚死锁链中的某一个事务,让其他事务能够进行下去。 将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
请求资源的超时时间默认为50s,那这太长了,如何去设置这个时间是个大难题,超时时间过短会误伤大量正常等待的线程,如果超时时间过长,对服务来说,是无法接受的,所以一般采取第二种策略:死锁检测。
但是死锁检测也是有额外负担的,需要CPU去负责运行检测程序,如果并发量过大,还是会导致CPU的死锁检测占用大量的CPU资源。
针对于热点行更新的性能问题? 有如下方式:
- 如果能确保热点行不会出现死锁问题,可以临时关闭死锁检测,将参数innodb_deadlock_detect设置为off即可。
- 控制并发读,设定并发上限,至于并发的位置,可以是服务端,路由处,数据库中间件也行。
- 将行拆分出来,拆分为多个行,例如一个行是用来记录总金额的,现在我们用10行来一起记录总金额也很ok。
练习问题:
- 两阶段协议?依据该协议我们能做什么?
- 死锁产生的条件?
- MySQL如何解决死锁问题?
- 热点行更新的性能问题如何去解决?