1. 共享锁和独占锁
- 共享锁:简称S锁。在事务要读取一条记录时,需要先获取该记录的S锁。
- 独占锁:简称X锁。在事务要改动一条记录时,需要先获取该记录的X锁。
S锁和X锁的兼容关系:
| X | S | |
|---|---|---|
| X | 不兼容 | 不兼容 |
| S | 不兼容 | 兼容 |
2. 意向锁
InnoDB支持多粒度锁定,即表级锁、行级锁,这种锁定允许表级锁和行级锁同时存在。为了支持这种多粒度锁定,InnoDB提供了意向锁,意向锁表示事务希望在更细的力度上进行锁定。意向锁是由数据库自己维护的,当我们给一行记录添加S/X锁时,数据库会自动给该表添加IS/IX锁。
注:InnoDB只支持表级别的意向锁。表级别的意向锁和行级锁不会产生冲突。
- 意向共享锁:事务想要获得一张表中某几行的共享锁。
- 意向排它锁:事务想要获得一张表中某几行的排它锁。
主要用处:通过意向锁可以快速地判断表中是否有记录被锁住,从而避免通过遍历的方式判断表中是否有记录被上锁,提升加锁的效率。
比如:事务A想要对某个表上X锁,这个时候如果表中存在被加了行级别的X锁或者S锁,则加锁会失败,此时直接根据意向锁就能知道这张表是否有行级别的X锁或者S锁。
3. InnoDB行锁
InnoDB存储引擎有3中行锁的算法:
- Record Lock:当个行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:Record Lock + Gap Lock,锁定一个范围,并且锁定记录本身
- 当查询的索引有唯一属性时,会降级为Record Lock。
假如有张表z
create table z (a int, b int, primary key(a), key(b))insert into z select 1, 1;insert into z select 3, 1;insert into z select 5, 3;insert into z select 7, 6;insert into z select 10, 8;
在Next-Key Lock的算法下
Nexy-Key Lock 加锁的原则和优化(From 丁奇《MySQL45讲》):
- 原则1:加锁的基本单位是Nexy-Key Lock。Nexy-Key Lock是前开后闭区间。
- 原则2:查找过程中访问到的对象才会加锁。
- 优化1:索引上的等值查询,给唯一索引加锁的时候,Nexy-Key Lock退化为Record Lock。
- 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,Nexy-Key Lock退化为间隙锁。
- 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
使用 select * from z where a = 3 for update 会给 a = 3 这一行加上Record Lockwhy:根据Next-Key Lock, 本来是要锁 (1,3] 的,但是因为a是唯一索引,所以会降级为Record Lock。使用 select * from z where a = 2 for update 会给 a (1,3) 加间隙锁why:根据Next-Key Lock,给(1,3]加锁,但是因为3!=2,,Nexy-Key Lock退化为间隙锁。使用 select * from z where b = 3 for update 会给b(1,3],(3,6)加锁,会给a=5加Record Lock为什么会加(3,6)?因为b是普通索引,索引访问到3之后不能停止,需要继续直至访问到了6,此时给(3,6]加Next-Key Lock,由于上述优化2,退化成间隙锁(3,6)
InnoDB存储引擎在REPEATABLE READ隔离级别下使用Next-Key Locking来加锁,在READ COMMITTED隔离级别下仅使用Record Lock。
4. MySQL是如何解决幻读的?
幻读是指在同一事务下,连续执行两次同样的SQL查询语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。
当前读:采用Next-Key Locking来避免。比如对于select from z where a > 2 for update 语句,其锁住的是(2,正无穷)这个范围,任何对这个范围的插入都是不被允许的,从而避免了幻读。
*快照读:使用MVCC解决。
