行锁概念
行锁是由不同引擎自己实现,Innodb支持,MyISAM不支持。
行锁是针对数据表中行记录的锁,事务A更新了一行,B如果要向更新这一行,需要等事务A更新完毕。
两阶段锁:事务A更新1和2行,之后事务B更新1行,就算先更新好1行了,由于2行还没更新,只有在事务Acommit后,事务B才能进行更新,也就是说行锁是需要的时候加上的,不要的时候先不释放,等到事务整体commit结束才释放。
因此事务中需要锁多行,要把最可能造成锁冲突,最可能影响并发的锁往后放。
1从顾客 A 账户余额中扣除电影票价; 2给影院 B 的账户余额增加这张电影票价; 3记录一条交易日志。
把2排到最后,影响最小,提高了并发。
死锁和死锁检测
并发系统不同线程出现循环资源依赖,线程都在等别的线程释放资源,导致线程无限等待称为死锁。
两种策略处理死锁:
-
设置超时时间(innodb_lock_wait_timeout)
-
死锁检测发现死锁,主动回滚死锁链条的某一个事务,让其他事务继续进行(innodb_deadlock_detect=on)
innodb_lock_wait_timeout默认值位50s,时间过长,但是设置值较小时,会误伤一些等待锁,因此正常采用主动死锁检测。
但是每个事务被锁,就要检查它以来的线程是否被别人锁住,是否循环等待行程死锁,如果多个事务更新同一行,都判断自己加入会不会死锁,时间复杂度O(n), 相当一个一个线程加入要1+2...+n,因此总体时间复杂度位O(n^2),导致cpu利用率很高。
结局方案有两种
-
如果能保证这个业务不会出现死锁,可以把临时死锁关掉,但是有一定风险,可能会出现一些超时(50s等待时间)
-
控制并发度。比如同一行只能十个线程在更新,死锁检测成本低,客户端做并发控制不可行,因为和客户端很多,就算每个客户端只有五个并发线程,600个客户端峰值并发也有3000,因此要有中间件,对于相同行的更新,进入引擎前排队。当然也可以将一行逻辑改成多行来减少锁冲突,以电影账户为例,可以考虑放10个总额记录,10个加起来是总和,这样随机选一条来加,冲突概率降到1/10,但是要考虑到退票逻辑,如果行值小于0,做特殊处理。