1.1 锁的类型
共享锁(读锁),允许事务读一行数据。
排他锁(写锁),允许事务删除或更新一行数据。
1.2 粒度锁
Lock锁根据粒度主要分为表锁、页锁和行锁。MySQL 不同的存储引擎支持不同的锁机制:
MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking)
BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁
InnoDB 存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
1.3 总结
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
2. InnoDB中的锁
2.1 行锁
InnoDB 实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
2.2 意向锁
为了支持多粒度锁定,InnoDB 存储引擎引入了意向锁(Intention Lock),每次加行锁前都会加意向锁。意向锁是表级锁,目的是在锁定该表前不必检查各个行的行锁,而只需检查表上的意向锁
。可以知道RR隔离级别下的表锁有4钟(S、X、IS、IX)。
意向共享锁(IS):事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。
意向互斥锁(IX):事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。
意向锁是 innodb 自动加的,不需要用户干预。对于 update、delete 和 insert 语句,innodb 会自动给涉及数据集加排它锁(X);对于普通 select 语句,innodb 不会加任何锁。事务可以通过以下语句显式给记录集加共享锁或排它锁。
共享锁(S):select * from table_name where … lock in share mode。
排它锁(X): select * from table_name where … for update。
表级锁的兼容性
当前锁模式/是否兼容/请求锁模式 | X | IX | S | IS |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
- S是表的(不是行的)共享锁,X是表的(不是行的)排它锁。
- 意向锁和意向锁不冲突,意向锁和行锁不冲突。
2.3 MVCC行级锁和一致性非锁定读(consistent nonlocking read)
InnoDB采用多版本并发控制
(MVCC, multiversion concurrency control)来增加读操作的并发性。MVCC是指,InnoDB使用基于时间点的快照来获取查询结果,读取时在访问的表上不设置任何锁,因此,在事务T1读取的同一时刻,事务T2可以自由的修改事务T1所读取的数据。这种读操作被称为一致性非锁定读。这里的读操作就是普通SELECT。
隔离级别为RU和Serializable时不需要MVCC,因此,只有RC和RR时,才存在MVCC,才存在一致性非锁定读。
- RC隔离级别,每次select都是获取一个最新的快照(所以,RC时总是可以读取到最新提交的数据)。
- RR隔离级别,同一个事务内的所有的一致性读总是读取该事物下第一个select获得的快照。
2.4 锁定读(locking read)
InnoDB提供两种锁定读,即:SELECT … FOR SHARE 和 SELECT … FOR UPDATE。
使用这两种锁定读查询数据时会设置排它锁或共享锁。此外,如果当前隔离级别是RR,它还会在每个索引记录前面的间隙上设置排它的或共享的gap lock(排它的和共享的gap lock没有任何区别,二者等价)。
2.3 InnoDB 行锁实现方式
InnoDB 行锁是通过给索引上的索引项加锁来实现的
。只有通过索引
条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁。- 不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
3. Record Lock(记录锁)
单个行记录上的锁,Record Lock
总是会去锁住索引记录,如果InnoDB存储引擎表建立的时候没有设置任何一个索引,这时InnoDB存储引擎会创建隐藏的聚集索引,并使用该索引执行记录锁。
LOCK_MODE:S,REC_NOT_GAP
或X,REC_NOT_GAP
。
4. Gap Lock(间隙锁)
索引记录之间的间隙上的锁,锁定尚未存在的记录,即索引记录之间的间隙。间隙锁一定是开区间,间隙锁在本质上是不区分共享间隙锁或互斥间隙锁的,而且间隙锁是不互斥的。
gap lock存在的唯一目的就是阻止其他事务向gap中插入数据行,它用于在隔离级别为RR时,阻止的幻读的产生
。
LOCK_MODE:S,GAP
或X,GAP
。
对于唯一索引
:- 等值检索:Next-Key Locks就退化为记录锁,不会加gap锁。
- 范围检索:会锁住检索范围内的记录锁和gap锁。
非唯一索引
:- 等值检索:记录锁,索引记录之间的间隙。
- 范围检索:范围的记录锁和范围内索引记录之间的间隙。
非索引
:全表gap锁和记录锁。
5. Next-Key Lock(临键锁)
它锁定索引记录以及该索引记录前面的间隙,是一个左开右闭的区间,有shard或exclusive两种模式。在Repeatable Read隔离级别下,Next-key Lock 算法是默认的行记录锁定算法。
LOCK_MODE:S
或X
。
6. Insert Intention Locks(插入意向锁)
一种特殊的gap lock。在插入操作之前,INSERT操作会首先在索引记录之间的间隙上设置insert intention lock,该锁的范围是(插入值, 向下的一个索引值)。有shard或exclusive两种模式,但,两种模式没有任何区别,二者等价。
LOCK_MODE:S,GAP,INSERT_INTENTION
或X,GAP,INSERT_INTENTION
。
- gap lock会阻塞insert intention lock,事务执行一个语句后生成gap lock后在gap lock区间执行插入语句会阻塞。事实上,gap lock的存在只是为了阻塞insert intention lock。
- gap lock相互不会阻塞。
- insert intention lock相互不会阻塞。
- insert intention lock不会阻塞gap lock。
3. 优化
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
- 合理设计索引,尽量缩小锁的范围。
- 尽可能较少检索条件,避免间隙锁。