InnoDB|S 锁与 X 锁的爱恨情仇《死磕MySQL系列 四》( 二 )


在Innodb存储引擎中 , 行锁是在需要的时候加上的 , 但并不是不需要了就直接释放的 , 而是要等到事务结束才释放 。
案例:解释两阶段锁

上图中MySQL1客户端开启事务并执行了两条update语句 , 紧接着MySQL2开启另一个事务执行update语句 , 那么此时MySQL的更新语句会执行成功吗?
答案肯定是不能的 。
这个结论取决于MySQL1事务在执行完两条 update 语句后 , 持有哪些锁 , 以及在什么时候释放 。 你可以验证一下:MySQL2事务 update 语句会被阻塞 , 直到MySQL1事务 执行 commit 之后 , 才能继续执行 。
万事有因必有果 , 有头必有尾 , 锁是开启事务后添加的也需提交事务后解除 。
现在你理解了两阶段锁 , 那么试想一下对你在写代码有什么帮助吗?
三、理解死锁
这幅图是咔咔在2019年画的 , 当时用这种方式来解释死锁对于一部分伙伴来说属实有点绕 。
错误的理解:之前在一个博文中看到对死锁是这样解释的
现实中这样的案例比比皆是 , 家里有两个小孩 , 给老大冲了一杯奶 , 这时老二过来也想喝 。 但奶嘴只有一个 , 此时老二只能处于等待状态 , 让老大先喝完 。 这个就是死锁 。
不要把锁等待跟死锁一同对待 , 锁等待是 , 一个事务中的语句添加了共享锁 , 另一个事务开启了排它锁 。 此时就需要等待共享锁的释放 , 这个过程是锁等待 。 而死锁是两个事务互相等待对方 。
四、优化你的代码尽量防止死锁知道两阶段锁后 , 在以后的代码实现中要把最可能造成锁冲突也就是死锁的语句放到最后边 。
问题:如何理解放到最后边这句话?
这样一个业务场景 。
每到中午吃饭时间都是好几个人一起出去 , 吃饭得付钱吧!复现一下这个流程 。
1.你给商家付了10块钱 , 这笔钱从你的余额中扣 。
2.给商家的账户添加10元 。
3.记录一条交易日志 。
在这个过程中可得知进行了两次update操作 , 一次insert操作 。 使用为了保证交易的原子性数据的一致性此时必须得把三个操作放到一个事务 。
在这三个操作中最容器造成锁冲突的就是第2步给商家的账户添加钱 。
所以在编码过程中需要把第2步放到最后一步执行 , 保证在同样结果下锁住的时间最短 。 这样可以在编码的程度上尽量保证事务之间锁等待 , 提高事务并发度 。
五、解释死锁的两种方案第一种方式
MySQL已经给咱们提供好了 , 使用参数innodb_lock_wait_timeout来设置超时时间 。 若等待时间超过设置的值则返回超时错误 。
在MySQL8.0版本中此值默认为50s , 意味着当出现死锁以后 , 被锁住的线程需要50s才会自动退出 , 然后其它线程才会继续执行 。 这个等待时间一般是无法接受的 。
但设置时间太短会造成很多锁等待的语句直接返回超时 , 造成严重误伤 。
重要的话再说一遍:“不要把锁等待跟死锁一同对待 , 锁等待是 , 一个事务中的语句添加了共享锁 , 另一个事务开启了排它锁 。 此时就需要等待共享锁的释放 , 这个过程是锁等待 。 而死锁是两个事务互相等待对方 。
第二种方式
另一个种方式 , 同样MySQL也给提供了一个参数innodb_deadlock_detect , 默认值为on , 意思是当发现死锁后 , MySQL主动回滚死锁链条中的某一个事务 , 让其他事务得以继续执行 。
检测死锁的流程是当一个事务被堵住时 , 就要看它所在的线程是否被别的线程锁住 , 如若没有则继续找下一个线程进行检测 , 最后判断是否出现了循环等待 , 也就是死锁 。