7.行锁功过
MYSQL的行锁是引擎层的,由各个引擎来实现,InnoDB支持行锁,而MYSQL原生的存储引擎MyISAM不支持行锁,也不支持事务,这是它被InnoDB取代的一个重要原因。
首先行锁是添加在索引上,如果where关键字后面的列没有索引,那么会导致锁表。
两阶段锁
行锁在需要用到的时候会被获取,但是并不会立刻释放,而是在事务提交的时候释放,这就是两阶段锁。
如图所示,假设id为表T主键,那么上图的事务会有什么结果?
事务B的操作会被阻塞,等待事务A commit之后才会执行。
知道了两阶段锁的特性之后,我们可以把最容易造成锁冲突,或者容易锁住多行的SQL语句放在合适的执行顺序上。
死锁和死锁检测
上图中的事务A等待事务B释放id=2的行锁,事务B等待事务A释放id=1的行锁,两个事务产生锁资源循环依赖,造成死锁(无期限等待)。
如何避免死锁
一般有两种策略:
1.直接进入等待,直到超时,可以设置参数innodb_lock_wait_timeout,默认值为50S
2.死锁检测,发现死锁后,主动回滚死锁链中的某一个事物,使其他事物可以继续执行,可以设置参数innodb_daedlock_detect,默认开启
第一种策略,默认时间为50S,对于线上业务来讲,显然是不可接受的,但是我们也不能把时间设置的太短,如1S,可能有一些事物只是在正常的锁等待,容易造成误伤。
所以我们一般情况下还是使用第二种策略,即:死锁检测,而且死锁检测默认开启,但是死锁检测也是有额外负担的。
每当有事务被锁住的时候,都会去检测它锁依赖的线程有没有被别人锁住,如此循环,看有没有出现循环等待时间,即死锁。
如果有一个线程锁住了某一行纪录,之后有1000个线程要获取这一行记录,那么这个时候,死锁检测的操作就是一百万量级的,这时候就会看到cpu的使用率非常高,但是可能系统的QPS很低,也就是处理事物的量很低。
如何避免因死锁造成的性能损失
关闭死锁:
如果确定系统不会产生死锁,那么可以主动关闭死锁检测。但是这种方案有风险。
控制事务并发度:
我们可以降低事务的并发度,可以在服务端控制。
在设计上降低并发度,比如给某一个热点字段分成10个字段,每次随机写某一个字段,这样并发就减少为原来的十分之一,不过这样代码也需要考虑更多,需要做一些特殊处理。
小结:
本节学习了行锁的两阶段锁协议,死锁和死锁检测,以及如何避免因死锁检测造成的性能损失。
我们了解了两阶段锁协议以后,我们可以把会所住多行的SQL操作放到事务的最后执行,以减少死锁的概率。