1、简述
MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如MyISAM 引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB 是支持行锁的。
行锁其实就是针对数据库数据行的锁定,同一时刻只能有一个事务去更新此行,如果有多个事务更新此行,要求顺序commit事务。
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
行锁的过程中是容易产生死锁的。
例如:
事务一:
update student set name = xiaoming where id =1
update student set name = yang where id = 2
事务二:
update student set name = zhang where id = 2
update student set name = huahua where id =1
在这两个事务中,事务一拿到了student 表ID= 1的行锁,并且更新了,与此同时,事务二拿到了student表ID = 2的行锁,这时事务一在等待事务二释放ID = 2 的行锁,事务二也在等待事务一拿到的ID = 1的行锁。这样相互的等待,并且执行不下去,就产生了死锁。
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致
这几个线程都进入无限等待的状态,称为死锁。
我们需要怎么解决mysql中的死锁呢?
方法一:以通过mysql参数innodb_lock_wait_timeout 来设置。设置这个参数可以在事务线程等待多少秒后拿不到依赖线程,就认定产生了死锁。超时后主动释放。
方法二:发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on。
但是这两个方法都不完美,可以解决部分问题,但又会产生一些新的问题。方法一最暴力,直接设置超时时间,但是这超时时间是由人去设定的,设置短了会产生误杀(有的线程执行时间确实很长,但不是死锁的原因),设置长了不可能我们一个HTTP相应执行好几十秒。方法二确实能有效的检测死锁,但是他会把当前事务相关的线程都检查一遍(新加入得处于timewait线程都要检查),要是本来一个行的更新就频繁,并发大。那么需要检测线程就多了,时间花费暂且不谈,cpu的消耗是巨大的,有可能直接拉死系统。
那么有没有最好的解决方案呢?答案是没有最好,只有更好,需要设计更好的防治死锁,我们需要上面两个方法都开启,并且在设计数据库的访问时通过一些手段去避免方法二带来的CPU的巨大消耗。在些业务或者设计的时候就应该避免死锁,还有在实现业务的时候,避免某一条数据行的更新过于频繁和控制数据行更新的并发度(在服务端使用中间件技术)等等一些手段。