根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类。
全局锁
使用命令:Flush tables with read lock 或者set global readonly=true(推荐使用前者,因为后者的readonly字段可能要被用来做逻辑判断,并且前者锁支持手动和自动释放,但后者必须再次手动设置readonly,容易造成长事务)
使用场景:全库逻辑备份
拥有事务功能的引擎使用single-transaction(事务+MVCC)进行全局锁,不支持事务就用Flush tables with read lock。
表级锁 (表锁与元数据锁MDL)
-
表锁:
lock tables t1 read, t2 write……
lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象 -
MDL:
MDL 不需要显式使用,在访问一个表的时候会被自动加上。对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。所以对表进行加减字段时容易导致线程阻塞(进行中的事务需要释放完所有读写锁MDL才能获取写锁,即写锁包含有读锁,并且MDL等待获取锁的过程中后续的事务不能开启)
行级锁
MySQL中的行锁是加在索引文件上的,所以当查询没有用上索引时,行锁不会生效转而变成表锁。
注意:当使用for update进行大量范围查询时,需要在索引上反复加多个行锁,大量消耗CPU资源,MySQL可能会自动优化放弃使用行锁,直接给全表加锁,可能这样查询速度会更快.。 |
并不是所有引擎都支持行锁
两阶段锁协议:在 InnoDB 事务中,行锁是在需要的时候才加上的(执行对应行的sql语句时),但并不是不需要了就立刻释放,而是要等到事务结束时才释放。(所以如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放,如果往前放的话事务锁住该行的时间就变长了)
死锁(看到下面这张图的时候感觉自己以前对死锁的理解更清楚了)
解决死锁的策略:
- 直接进入等待,直到超时。(超时时间不好设置,不建议使用)
- 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。一般建议用主动死锁检测策略(默认开启),但是会消耗大量的cpu资源,特别是热点更新(特定的行记录被访问的频率高),解决办法可以通过使用中间件,对于向同行的更新进行排序。
ps: 锁这块理解的不深,后面一边实践一边继续学习。
参考文章:
https://time.geekbang.org/column/article/70215