InnoDB 介绍
InnoDB 中的锁
你可能听过各种各样的 InnoDB 的数据库锁,Gap 锁,共享锁,排它锁,读锁,写锁等等。但是 InnoDB 的标准实现的锁只有 2 类,一种是行级锁,一种是意向锁。
InnoDB的行级锁:
- 共享锁(读锁 S Lock),允许事务读一行数据。
- 排它锁(写锁 X Lock),允许事务删除一行数据或者更新一行数据。
行级锁中,除了 S 和 S 兼容,其他都不兼容。
InnoDB 支持两种意向锁(即为表级别的锁):
- 意向共享锁(读锁 IS Lock),事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
- 意向排他锁(写锁 IX Lock),事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排它锁前必须先取得该表的 IX 锁。
首先解释一下意向锁,意向锁为了表明某个事务正在锁定一行或者将要锁定一行数据。
首先申请意向锁的动作是 InnoDB 完成的,怎么理解意向锁呢?
例如:事务 A 要对一行记录 R 进行上 X 锁,那么 InnoDB 会先申请表的 IX 锁,再锁定记录 R 的 X 锁。在事务 A 完成之前,事务 B 想要来个全表操作,此时直接在表级别的 IX 就告诉事务 B 需要等待而不需要在表上判断每一行是否有锁。
意向排它锁存在的价值在于节约 InnoDB 对于锁的定位和处理性能。另外注意了,除了全表扫描以外意向锁都不会阻塞。
意向锁的存在意义
如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。
假设 我们有一张users表
id | name |
---|---|
1 | zhangsan |
2 | lisi |
当事务A 想要更新id=2的user 的时候,会做以下操作
- DB引擎给表增加一个意向排它锁
- 事务A给id=2的行增加一个排它锁
这里的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!
此时若有事务B想获取 User表的共享锁(注意,这里是表级别的锁) 这个时候,因为是要锁表,所以意味着表中的所有数据都不能修改,但是此时A 已经对2 加了锁,所有如果没有意向锁,事务B 必须在获取表级别的共享锁的时候,检测每一行是否有排他锁。
但是一旦有了意向共享锁,所以事务B 因此不用每一行都扫描。
因此此时事务 B 检测事务 A 持有 users 表的意向排他锁,就可以得知事务 A 必然持有该表中某些数据行的排他锁,那么事务 B 对 users 表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排他锁。
我们可以根据下表来看一下各个锁的兼容性
锁的兼容互斥性
看一下意向锁的排斥性
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
意向共享锁(IS) | 兼容 | 兼容 |
意向排他锁(IX) | 兼容 | 兼容 |
意义: 意向锁不会跟对意向锁产生排斥,这设计到表的并发。
举个例子:还是刚才的User 表
事务A 要对id = 2 的表进行更新,那么事务A 会产生两个效果
- 表级别的意向排它锁
- 行级别的排他锁。
此时B 事务想更新 id = 1 的行, - 首要获取User 的意向排它锁,
- 发现User 表已经有意向排它锁
- 但是意向排他锁是互相兼容的,因此,事务B 加锁成功
- id = 1的数据行上不存在任何排他锁,最终事务 B 成功获取到了该数据行上的排他锁。
意向锁对共享和排他锁的互斥性
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
共享锁(IS) | 兼容 | 互斥 |
排他锁(IX) | 互斥 | 互斥 |
锁的算法
InnoDB 有 3 种行锁的算法:
- Record Lock: 单个行记录上的锁。
- Gap Lock: 间隙锁,锁定一个范围,而非记录本身。
- Next-Key Lock:结合 Gap Lock 和 Record Lock,锁定一个范围,并且锁定记录本身。主要解决的问题是 RR 隔离级别下的幻读。
Next-Key Lock
和 Gap Lock
的区别就是:会首先对索引(如果我们的查询语句命中索引记录的话)记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
如果没有命中索引记录,则直接退化成 Gap Lock
Gap Lock 和 Next-Key Lock
- 产生间隙锁的条件(RR事务隔离级别下;):
- 使用普通索引锁定;
- 使用多列唯一索引;
- 使用唯一索引锁定多行记录。
- 不使用索引则加全局锁
- 举例说明,假若我们有如下表
id(主键) | v1(索引) | v2(无索引) |
---|---|---|
1 | 1 | 0 |
2 | 3 | 1 |
3 | 4 | 2 |
5 | 5 | 9 |
7 | 7 | 4 |
10 | 9 | 5 |
间隙锁(Gap Lock)一般是针对非唯一索引而言的,test表中的v1(非唯一索引)字段值可以划分的区间为:
-∞,1)(1,3)(3,4)(4,5)(5,7)(7,9)(9, +∞)
假如要更新v1=7
的数据行,锁定的区间是(5,7)和(7,9)。同时找到,v1=7的数据行的主键索引和非唯一索引,对key加上锁。
-
插入锁
事物1 事物2 UPDATE test set v2=10 where v1=7(加锁范围(5,9)) insert into test values(15,6,3)(阻塞)) insert into test values(9,9,3)(阻塞)) insert into test values(11,9,3)(成功)) insert into test values(4,5,3)(成功)) 看一下 插入图
-
更新锁
事物1 事物2 UPDATE test set v2=10 where v1=7(加锁范围(5,9)) update test set v1=6 where v1=5(阻塞)) update test set v1=9 where v1=7(阻塞)) update test set v1=10 where v1=9(成功)) v1 = 6 相当于在 5 7 范围内加入
锁选择
- 1)、如果更新条件没有走索引,此时会进行全表扫描,扫表的时候,要阻止其
他任何的更新操作,所以上升为表锁。 - 2)如果更新条件为索引字段,但是并非唯一索引(包括主键索引)那么此时
更新会使用Next-Key Lock。使用Next-Key Lock的原因:
a)、首先要保证在符合条件的记录上加上排他锁,会锁定当前非唯一索引和对应的主键索引的值;
b)、还要保证锁定的区间不能插入新的数据。 - 3)、如果更新条件为唯一索引,则使用Record Lock(记录锁)。
锁应用
- 在不通过索引条件查询时,InnoDB 会锁定表中的所有记录。所以,如果考虑性能,WHERE语句中的条件查询的字段都应该加上索引。
- InnoDB通过索引来实现行锁,而不是通过锁住记录。因此,当操作的两条不同记录拥有相同的索引时,也会因为行锁被锁而发生等待。
- 由于InnoDB的索引机制,数据库操作使用了主键索引,InnoDB会锁住主键索引;使用非主键索引时,InnoDB会先锁住非主键索引,再锁定主键索引。
- 当查询的索引是唯一索引(不存在两个数据行具有完全相同的键值)时,InnoDB存储引擎会将Next-Key Lock降级为Record Lock,即只锁住索引本身,而不是范围。
- InnoDB对于辅助索引有特殊的处理,不仅会锁住辅助索引值所在的范围,还会将其下一键值加上Gap LOCK。
- InnoDB使用Next-Key Lock机制来避免Phantom Problem(幻读问题)。
参考
- MySQL探秘(七):InnoDB行锁算法
- 快速理解脏读、不可重复读、幻读
- 扫盲贴-分布式数据一致性:两阶段提交,三阶段提交
- 常用的分布式事务解决方案
- 事务并发的可能问题与其解决方案
- MySQL的可重复读级别能解决幻读吗
- MySQL的InnoDB的幻读问题
- Innodb 中 RR 隔离级别能否防止幻读?
- 浅谈数据库并发控制 - 锁和 MVCC
- MySQL探秘(六):InnoDB一致性非锁定读
- 一文快速搞懂MySQL InnoDB事务ACID实现原理
- InnoDB逻辑存储结构
- InnoDB数据页结构
赞赏一下