- 什么是行锁?
- 两阶段锁
- 死锁和死锁检测
写在最前,本篇文章来源于对MySQL实战45讲的笔记,主要是为了帮助自己理解。如果同时还能对其他人有所裨益,那就更好不过了。如果有谬误的地方,还请不吝指出。
本文并非对文章的直接复制,并且肯定有理解不到位的情况,如果希望系统地学习,还是要去官网支持原作者。
注意:最好拥有一定的MySQL基础再来看本系列文章,可以去b站搜索动力节点的mysql基础教程,或者翻看我做的走进MySQL系列(笔记做的并不是特别详尽,仅作为参考)
什么是行锁?顾名思义,行锁就是指,对某一行进行更新时,这一行的记录会被锁住,等到更新完成以后,其它事务才能对这一行进行更新。
行锁是针对一条记录的。
**MySQL的行锁是在引擎层实现的,而MyISAM不支持行锁。**只能使用表锁。
两阶段锁行锁是什么时候取得和释放的呢?
取得肯定是在更新时,然而释放锁却是在事务结束以后。这也就是所谓的两阶段锁协议。
为什么呢?
因为不确定这个更新是否一定会被提交,万一要回滚呢?
如果这时候被另一个事务获取到锁,并更新了的话,即使这个事务回滚了,更新依旧被保留了下来。
根据这个特点,在实际情况中,我们尽量将并发度高的语句放在事务最后执行,这样操作和释放锁的间隔时间会被压到最短。最大程度减少了事务间的锁等待。
死锁和死锁检测行锁还会导致死锁,怎么出现的?
联想一下java中出现死锁的情况
- 事务A对记录1更新;同时,事务B对记录2更新;
- 事务A更新完成,并请求记录2的行锁;事务B更新完成,并请求记录1的行锁。但由于AB现在还未提交,双方都无法取得;
- 要想获得记录2的行锁,必须等待事务B提交,然而事务B提交必须等到事务B获取记录1的行锁。此时死锁就产生了。
两种解决方案:
- 超时等待:通过innodb_lock_wait_timeout来设置,默认值50s。如果出现死锁,要等待50s。
- 死锁检测:发现死锁后,主动回滚死锁链条中的某一个事务,使得其它事务继续执行。通过设置innodb_deadlock_detect 为on开启逻辑。
50s显然不是一个能接受的数字,那死锁检测又有什么问题呢?
每一个事务被锁的时候,都要去看它所依赖的线程有没有被锁住,最后判断是否出现了循环等待(死锁)。比如:A被B锁住,A就去查看锁住B的线程,如此直到判断出为死锁,或者直到链条的最后一环才会终止
如果有多个事务更新同一行记录会怎么样?
这显然不会发生死锁,但由于行锁阻塞,线程依旧会判断是否由于自己的加入造成了死锁。
最直观的想法肯定是去检查现在持有这个锁的线程是否被锁住了,如果没被锁住,那说明可以安心获取锁,因为不会发生死锁。但可惜的是,由于行锁等待会形成一个队列,即使当前线程释放锁,也不一定轮得上这个线程,对于这个线程来说,问题还在于:自己获取锁以后,会不会和我后面的线程造成冲突呢。所以它要去访问处于自己后面的线程以查看是否会出现死锁。所以总的复杂度为O(n^2)。
如果有1000个并发线程更新同一行,那么死锁检测就是100万量级,CPU利用率很高,但却执行不了几个事务。
那怎么解决这类问题呢?
- 如果确保不会出现死锁,可以临时关掉死锁检测。风险是,死锁可以回滚,通过业务重试后就没问题了,是业务无损的。但如果关掉死锁检测,且出现了死锁,会长时间等待超时,是业务有损的。
- **控制并发度。**比如,使得一行记录同一时间最多只有10个线程更新,那么死锁检测的成本很低。可以通过客户端做并发控制,但如果客户端很多,即使每个客户端并发线程很少,汇总到服务端后,峰值也会很高。
所以,并发控制最好在服务端,思路是对于同行的更新,不在引擎内等待,即不让他进行死锁检测,而是在外部进行等待。
除此之外,还可以将一行数据放在多个记录上(即子账户的概念),当需要总数据时,可以汇总这些子记录,以得到总体值。



