InnoDB存储引擎的默认隔离级别事可重复读,MVCC多版本并发控制仅仅解决了快照读情况下的数据隔离,而对于当前读,InnoDB通过锁来进行并发控制。
InnoDB锁
本文主要参考了MySQL官方文档,并在上面添加了一些自己的理解,有兴趣看英文的也可以看MySQL官方文档。本文分为以下章节:
- 共享锁和独占锁;
- 意向锁;
- 行锁;
- 间隙锁;
- Next-Key锁
- 插入意向锁;
- 自增锁;
共享锁和排他锁
InnoDB锁的最小粒度是行锁,行锁可以分为两大类:共享锁(S)和独占锁(X)。
- 共享锁:持有某行数据共享锁的事务,可以读取行锁对应行的数据;
- 独占锁:持有某行数据独占锁的事务,可以修改行锁对应行的数据;
如果事务T1持有行R的共享锁,那么对于事务T2对行R的访问分为两种情况:
- 如果事务T2请求行R的共享锁,则事务T2可以请求成功,请求完成后事务T1和事务T2同时持有行R的共享锁;
- 如果事务T2请求行R的排他锁,事务T2会被阻塞,直到事务T1释放锁或者事务超时回滚;
如果事务T1持有行R的共享锁,那么不管事务T2请求R行的共享锁还是排他锁,都会被事务T1阻塞,直到事务T1释放锁或事务T2回滚。
意向锁
InnoDB支持支持多种粒度的锁,比如对于以下两个SQL语句,加锁的对象就完全不同:
SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE
,其中ID是主键,ID=1的数据行存在,那么这句SQL会获取ID=1的数据行的独占锁;LOCK TABLES USER_INFO WRITE
,其中USER_INFO表存在,那么这句SQL会获取USER_INFO表的独占锁;
表锁和行锁之间也存在互斥的情况,比如表上的独占锁和表中每一行数据的独占锁之间冲突(锁表了当然不允许修改表中的内容),这种互斥要怎么实现呢?InnoDB使用了意向锁实现表锁和行锁之间的互斥,意向锁是表级别的锁,对一行数据添加独占锁或排他锁时,会先向数据行所在的表添加意向锁,意向锁分为两种类型:
- 共享意向锁:事务会对表中的某一行数据添加共享锁;
- 排他意向锁:事务会对表中的某一行数据添加排他锁;
所以对表添加意向锁的情况也分两种:
- 如果事务需要获取某一行数据的共享锁,那么必然会首先获取数据所在表的共享意向锁,如SQL语句
SELECT * FROM USER_INFO WHERE ID = 1 LOCK IN SHARE MODE
会首先向表USER_INFO
添加共享意向锁; - 如果事务需要获取某一行数据的排他锁,那么必然会首先获取数据所在表的排他意向锁,如SQL语句
SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE