InnoDB存储引擎-锁
1. 什么是锁
数据库系统使用锁是为了支持对共享资源进行并发访问, 提供数据的完整性和一致性.
2. 锁的类型
2.1 共享锁 (行锁)
共享锁 (S Lock), 允许事务读取一行数据.
如果一个事务 T1 已经获得了行 r 的共享锁, 那么另外的事务 T2 可以立即获得 r 行的共享锁, 因为读取并没有改变行 r 的数据, 这就是共享锁.
2.2 排他锁 (行锁)
排他锁 (X Lock), 允许事务删除或更新一行数据.
如果一个事务 T1 已经获得了行 r 的共享锁, 此时事务 T3 想获得 r 行的排他锁, 则必须等待 r 行上的其他食物释放了锁, 这种就是不兼容的排他锁.
X | S | |
---|---|---|
X | ❌ | ❌ |
S | ❌ | ✔️ |
2.3 意向共享锁 (表锁)
意向锁是将锁定的对象分为多个层次, 意向锁意味着事务希望在更细粒度上进行加锁.
意向共享锁 (IS Lock): 事务想要获得一张表中某几行的共享锁.
2.4 意向排他锁 (表锁)
意向排他锁 (IX Lock): 事务想要获得一张表中某几行的排他锁.
IS | IX | S | X | |
---|---|---|---|---|
IS | ✔️ | ✔️ | ✔️ | ❌ |
IX | ✔️ | ✔️ | ❌ | ❌ |
S | ✔️ | ❌ | ✔️ | ❌ |
X | ❌ | ❌ | ❌ | ❌ |
3. 一致性非锁定读
一致性的非锁定读 指的是 InnoDB 存储引擎通过行多版本控制的方式来读取当前数据库中的数据. 之所以叫 非锁定读, 是因为不需要等待访问的行上 X 锁的释放.
如果读取的行正在执行 DELETE 或 UPDATE 操作, 这时读取不会因此去等待行上锁的释放, 相反地, InnoDB 存储引擎会去读取行的一个快照数据.
一致性的非锁定读 只在 InnoDB 存储引擎的 读已提交(RC) 和 **可重复读(RR) **隔离级别下生效奥~ , 通过 MVCC 实现高并发下的数据一致性读.
快照: 快照数据是指该行的之前版本的数据.
- 该实现是通过 undo 段来实现的, undo 段本身就是事务中回滚用的, 因此快照数据本身是没有额外的开销的.
- 此外 快照数据是不需要上锁的.
- 读已提交 事务隔离级别下, 快照指的是每次读取的最新发数据; 可重复读 事务隔离级别下, 快照指的是读取事务开始时的行数据版本.
一行记录不可能只有一个快照数据, 一般称这种技术为多版本技术. 由此带来的并发控制, 称之为 多版本并发控制 (MVCC).
4. 一致性锁定读
默认的, InnoDB 存储引擎的 读已提交(RC) 和 **可重复读(RR)**隔离级别下, SELECT 操作使用的是一致性非锁定读.
但是在某些情况下, 用户需要显示地对数据库读取操作进行枷锁以保证数据逻辑的一致性. 整个时候就要加锁了奥~
InnoDB 存储引擎对于 SELECT 操作支持两种一致性的锁定读:
- SELECT — FOR UPDATE; (X锁)
- SELECT — LOCK IN SHARE MMODE; (S锁)
5. 锁的算法
5.1 行锁的 3 种算法
- Record Lock : 单个行记录上的锁.
- Gap Lock : 间隙锁, 锁定的是一个范围, 但不包括记录本身.
- Next-Key Lock: 临键锁 Gap Lock + Record Lock , 锁定一个范围, 并且锁定记录本身.
Gap Lock 的作用是为了防止多个事务将记录插入到同一个范围内.
Next-Key Lock 锁设定的目的是为了解决幻读问题.
对于主键(聚集索引)进行加锁查询时候, 仅加 Record Lock.
对于辅助索引(非聚集索引)进行加锁查询的时候, 其加的是 Next-Key Lock , 并且会对辅助索引下一个键值加上 gap lock.
当加锁查询的索引含有唯一属性时(主键或者唯一索引列), InnoDB存储引擎会对 Next-Key Lock 进行优化, 将其降级为 Record Lock , 仅锁住索引本身, 而不是范围.
5.2 表锁
意向共享锁
意向排它锁
自增所
元数据锁
6. 并发带来的问题
6.1 脏读 (违反隔离性)
脏读: 一个事务读取到了另一个事务未提交的数据.
通过使用 MVCC 的 ReadView 解决脏读。
6.2 不可重复读 (违反一致性)
不可重复读: 一个事务内多次读取统一数据集, 在这个事务还没有结束的时候, 另外一个事务也访问该统一数据集, 并作了一些DML操作. 因此在第一个事务种的两次读取之间由于其他事务的修改, 那么第一个事务两次读取的数据可能是不一样的.
其通过使用 MVCC 来避免不可重复读.
6.3 幻读
幻读是指在同一事物下, 连续两次同样的 SQL 语句可能导致不同的结果, 第二次 SQL 语句可能返回之前不存在的行.
采用 Next-Key Lock 机制来避免幻读问题.
7. 死锁
7.1 概念
死锁是指两个或两个以上的事务在执行过程中, 因争夺锁资源而造成的一种相互等待的现象. 若无外力作用, 事务都将无法等待下去.
7.2 解决死锁
- 超时机制
超时机制是解决死锁最简单的一种方法, 当两个事务互相等待时, 当一个事务的等待时间超过阈值时, 其中一个事务回滚, 另一个事务就能继续工作了.
- wait-for graph (等待图)
当前数据库普遍采用 等待图 的方式来进行死锁检测, 与超时机制比 这是一种更为主动的死锁检测方式. InnoDB存储引擎采用的就是这种方式.
wait-for graph 是一种较为主动的死锁检测机制, 在每个事务请求锁并发时都会判断是否存在回路, 若存在则有死锁, 通常 InnoDB存储引擎 选择回滚 undo 量最小的事务.
8. 其他
从 InnoDB1.0 开始, 在 INFORMATION_SCHEMA 架构下添加了表 INNODB_TRX, INNODB_LOCKS, INNODB_LOCK_WAITS. 通过这三张表, 用户可以更简单地监控当前事务并分析可能存在的锁问题. (P255)
9. MySQL5.7 是怎么解决脏读 不可重复读 幻读问题的
MySQL 5.7 的 InnoDB 存储引擎通过 多版本并发控制(MVCC) 和 锁机制 解决脏读、不可重复读和幻读问题。具体实现依赖于事务的隔离级别,以下是不同问题的解决机制:
9.1 脏读解决方式
- 读未提交:未采取任何措施, 允许脏读。
- 读已提交 及 可重复读:
- MVCC 通过生成一致性快照(Read View),确保事务只能读取已提交的数据版本。
- 未提交的数据对其他事务不可见。
- 串行化: 加锁.
9.2 不可重复读解决方式
- 读已提交:
- 每次查询生成新快照,可能读到其他事务已提交的修改,因此允许不可重复读。
- 可重复读:
- MVCC 在事务的第一次查询时生成一致性快照,后续所有读操作都基于该快照,保证事务内数据版本一致。
- 其他事务对数据的修改(即使已提交)对该事务不可见。
- 串行化: 加锁。
9.3 幻读解决方式
- 可重复读:
- MVCC:快照读(普通
SELECT
)基于一致性快照,事务内看不到其他事务插入的新数据。 - Next-Key 锁:
- 对当前读(如
SELECT ... FOR UPDATE
)或写入操作(如INSERT
、UPDATE
),InnoDB 使用 Next-Key 锁(记录锁 + 间隙锁),锁定范围内的记录和间隙,阻止其他事务插入或删除数据。 - 例如:执行
SELECT * FROM t WHERE id > 100 FOR UPDATE
时,会锁定id > 100
的现有记录和间隙,防止其他事务插入id > 100
的新数据。 - 可重复读没有避免所有的幻读。
- 对当前读(如
- MVCC:快照读(普通
- 串行化: 加锁。
9.4 关键点说明
- 可重复读下的幻读:
- 普通
SELECT
(快照读)通过 MVCC 不会出现幻读(基于事务开始时的快照)。 - 若使用 当前读(如
SELECT ... FOR UPDATE
)或写入操作,Next-Key 锁会阻止其他事务插入,从而彻底避免幻读。 - 例外场景:如果事务中混合使用快照读和当前读,可能出现逻辑上的幻读(需通过锁机制统一操作)。
- 普通
- 串行化:
- 所有读操作隐式加共享锁(
SELECT ... FOR SHARE
),退化为悲观锁,完全避免并发问题,但并发性能最差。
- 所有读操作隐式加共享锁(
9.5 总结
MySQL 5.7 的 InnoDB 通过以下组合解决并发问题:
- MVCC:解决脏读和不可重复读(RC 和 RR 级别)。
- Next-Key 锁:在 RR 级别下通过锁机制彻底解决幻读(针对当前读和写入操作)。
- 默认隔离级别(RR) 在大多数场景下平衡了性能和一致性,是生产环境的推荐选择。