Administrator
发布于 2022-07-15 / 0 阅读
0
0

RR 级别与幻读

RR 级别与幻读

在数据库事务隔离级别中,**RR(Repeatable Read,可重复读)级别在一定程度上可以解决幻读问

题,但并非完全解决**。以下是详细解析:



一、幻读的定义与产生场景

#### 1. 幻读的定义

幻读指在一个事务中,两次相同的查询操作返回的结果集不同(新增了记录),仿佛“幻觉”一般。例如:

  • 事务A查询表中id > 10的记录,得到结果集R1。
  • 事务B插入一条id=11的记录并提交。
  • 事务A再次查询id > 10的记录,得到结果集R2(包含R1和新插入的记录),即产生幻读。

  • #### 2. 幻读的本质

    幻读的核心是事务在查询时未锁定“未来可能插入的数据范围”,导致其他事务插入新数据后,原事务

    的查询结果发生变化。



    二、RR级别对幻读的处理机制

    #### 1. RR级别的基本特性

    RR级别通过MVCC(多版本并发控制)间隙锁(Gap Lock) 机制减少幻读的发生:

  • MVCC:每个事务读取的是数据的历史版本(快照),同一事务内多次查询返回相同结果(基于快
  • 照时间点的数据)。

  • 间隙锁:当查询条件涉及范围(如`WHERE id > 10`)时,会锁定查询条件对应的“数据间隙”,阻止
  • 其他事务在该间隙内插入数据。


    #### 2. RR级别对幻读的抑制作用

  • 当事务A在RR级别下执行范围查询时,会通过间隙锁锁定查询条件对应的范围(如id > 10的间隙)。
  • 若事务B尝试插入id=11的记录,会被间隙锁阻塞,直至事务A提交或回滚,从而避免事务A再次查询时
  • 出现新增记录(即幻读)。



    三、RR级别未完全解决幻读的情况

    #### 1. 幻读未被彻底解决的原因

    RR级别在特定场景下仍可能出现幻读,例如:

  • 使用唯一索引的范围查询:若查询条件基于唯一索引(如`WHERE id = 10`),RR级别仅使用行
  • 锁,不添加间隙锁,此时其他事务可插入符合范围条件的新记录(如id=11),导致幻读。

  • 无索引的范围查询:若表中无索引,RR级别会对全表加锁,此时不会出现幻读;但实际业务中,无
  • 索引的表查询性能极低,几乎不会使用。


    #### 2. 示例场景

  • 表结构:`CREATE TABLE test(id INT, name VARCHAR(10), INDEX idx_id(id));`
  • 事务A执行:`START TRANSACTION; SELECT * FROM test WHERE id > 10 FOR UPDATE;`(添加
  • 间隙锁锁定id > 10的范围)。

  • 事务B执行:`INSERT INTO test(id, name) VALUES(11, 'new');`(被间隙锁阻塞,无法插入)。
  • 但如果事务A的查询条件是`WHERE id = 10`(唯一索引查询),则仅锁定id=10的行,事务B可插入
  • id=11的记录,事务A再次查询时会看到新增记录,产生幻读。



    四、彻底解决幻读的隔离级别:Serializable

    #### 1. Serializable的机制

    Serializable(可串行化)级别通过强制事务串行执行,完全避免并发问题:

  • 任何事务在执行时,会锁定整个数据集,其他事务只能等待,直至当前事务完成。
  • 这种方式相当于将并发事务转为串行执行,彻底杜绝幻读、脏读、不可重复读等问题。

  • #### 2. Serializable的缺点

  • 性能开销极大:串行执行会导致数据库吞吐量大幅下降,仅在对一致性要求极高且并发量极低的场
  • 景中使用(如金融交易)。



    五、总结:RR级别与幻读的关系

    | 隔离级别 | 幻读解决能力 | 实现方式 | 性能影响 |

    |----------|----------------------------------|-----------------------------------|----------|

    | RR(可重复读) | 大部分场景下可抑制幻读,但非完全解决 | MVCC + 间隙锁(范围查询时) | 中

    等 |

    | Serializable(可串行化) | 完全解决幻读 | 事务串行化执行 | 极高 |


    #### 实际应用建议:

  • 大多数业务场景:使用RR级别,通过合理设计索引(如为范围查询条件添加索引)减少幻读风险。
  • 强一致性场景:若必须完全避免幻读(如库存扣减、金融对账),可考虑Serializable级别或在应用
  • 层配合分布式锁实现。



    InnoDB 存储引擎通过 Next-Key Lock( next-key 锁) 机制在可重复读(RR)隔离级别下实现对幻

    读和不可重复读的有效控制。Next-Key Lock 是 行锁(Record Lock)间隙锁(Gap Lock)

    的组合,其核心逻辑是锁定索引记录及其之间的间隙,从而防止其他事务在锁定范围内插入、更新或删

    除数据。以下是详细解析:



    一、Next-Key Lock 的定义与组成

    #### 1. 基本概念

  • Next-Key Lock = 间隙锁(Gap Lock) + 行锁(Record Lock)
  • 间隙锁:锁定索引记录之间的“间隙”,阻止其他事务在间隙内插入数据。
  • 行锁:锁定具体的索引记录,阻止其他事务修改或删除该记录。
  • 作用范围:锁定一个左闭右开区间 `[start, end)`,覆盖从 `start` 到 `end` 之间的所有索引值(不包
  • 含 `end` 本身)。


    #### 2. 索引与锁定范围的关系

    Next-Key Lock 的锁定范围依赖于表的索引结构,例如:

  • 表中有索引值 `10`、`15`、`20`,则 Next-Key Lock 可能锁定的区间为:
  • `(-∞, 10]`、`(10, 15]`、`(15, 20]`、`(20, +∞)`。



    二、Next-Key Lock 的工作原理

    #### 1. 在 RR 隔离级别下的默认行为

    当事务在 RR 级别执行 带条件的写操作(如 `SELECT ... FOR UPDATE` 或 `UPDATE`) 时,

    InnoDB 会自动使用 Next-Key Lock:

    1. 定位索引记录:根据查询条件找到对应的索引记录。

    2. 锁定记录与间隙:对命中的记录加行锁,并对记录前后的间隙加间隙锁,形成连续的锁定区间。


    #### 2. 示例:锁定范围演示

    假设表 `test` 有索引列 `id`,数据为 `1`、`3`、`5`,执行以下语句:

    START TRANSACTION;

    SELECT * FROM test WHERE id >= 3 FOR UPDATE;

  • Next-Key Lock 锁定区间
  • 命中记录 `id=3`,加行锁;
  • 锁定间隙 `(1, 3]` 和 `(3, 5]`,形成完整区间 `(1, 5]`。
  • 其他事务的限制
  • 无法插入 `id=2`(在 `(1, 3]` 间隙)、`id=4`(在 `(3, 5]` 间隙);
  • 无法修改 `id=3` 和 `id=5` 的记录。


  • 三、Next-Key Lock 对幻读的抑制作用

    #### 1. 防止幻读的核心逻辑

  • 当事务 A 执行范围查询时,Next-Key Lock 锁定查询条件对应的索引区间,阻止其他事务在该区间内插
  • 入新记录。

  • 例如:事务 A 查询 `id > 10`,锁定区间 `(10, +∞)`,事务 B 无法插入 `id=11`,避免事务 A 再次查询时
  • 出现新增记录(幻读)。


    #### 2. 与间隙锁、行锁的对比

    | 锁类型 | 锁定对象 | 防止的问题 | 在 RR 级别下的应用场景 |

    |--------------|------------------------|--------------------------|------------------------------|

    | 行锁(Record Lock) | 单个索引记录 | 其他事务修改当前记录 | 查询条件命中唯一索引的场景

    |

    | 间隙锁(Gap Lock) | 索引记录之间的间隙 | 其他事务在间隙内插入数据 | 范围查询(如 `id > 10`)

    |

    | Next-Key Lock | 索引记录 + 间隙 | 幻读、不可重复读 | 非唯一索引的范围查询 |



    四、Next-Key Lock 的优化:锁的降级

    InnoDB 会根据查询条件智能优化锁的类型,减少锁定范围:

    #### 1. 唯一索引查询时退化为行锁

  • 当查询条件命中唯一索引(如 `WHERE id = 10`,`id` 是主键或唯一索引),Next-Key Lock 会退化
  • 为行锁,仅锁定当前记录,不添加间隙锁。

  • 原因:唯一索引确保区间内不会有重复值,无需锁定间隙防止插入(插入重复值会被唯一约束拒
  • 绝)。


    #### 2. 示例:唯一索引场景

    -- 表结构:id 为主键(唯一索引)

    CREATE TABLE test(id INT PRIMARY KEY, name VARCHAR(10));

    -- 数据:id=1, 3, 5


    START TRANSACTION;

    SELECT * FROM test WHERE id = 3 FOR UPDATE; -- 仅锁定 id=3 的行,不锁定间隙

  • 其他事务可插入 `id=2` 或 `id=4`,因为间隙未被锁定(可能导致幻读,但需结合业务场景判断)。


  • 五、Next-Key Lock 引发的问题与解决方案

    #### 1. 可能引发的死锁

  • 场景:两个事务分别锁定不同的 Next-Key Lock 区间,并尝试获取对方的锁,导致死锁。
  • 示例
  • 事务 A 锁定 `(10, 20]`,事务 B 锁定 `(20, 30]`,双方同时尝试插入 `id=20` 时可能死锁。


    #### 2. 优化方案

  • 合理设计索引:为频繁查询的条件添加索引,避免全表扫描导致大范围锁定。
  • 缩小查询范围:用更精确的条件减少锁定区间(如 `WHERE id BETWEEN 10 AND 15` 比
  • `WHERE id > 10` 锁定范围更小)。

  • 降低隔离级别:若业务允许,可切换至读已提交(RC)级别,但需接受幻读风险。


  • 六、总结:Next-Key Lock 在 InnoDB 中的核心作用

    1. 锁定机制:组合行锁与间隙锁,锁定索引记录及其间隙,形成连续区间。

    2. 隔离级别的支撑:在 RR 级别下通过 Next-Key Lock 防止幻读,是 InnoDB 默认的并发控制手段。

    3. 性能与一致性的平衡

  • 优势:相比 Serializable 级别,RR + Next-Key Lock 允许更高的并发度。
  • 代价:可能因大范围锁定导致性能瓶颈,需通过索引优化减少锁定范围。

  • #### 实际应用建议:

  • 理解业务场景中的锁定需求,避免因 Next-Key Lock 导致的超时或死锁。
  • 通过 `EXPLAIN` 分析查询是否使用索引,避免无索引查询触发全表 Next-Key Lock。
  • 使用 `SHOW ENGINE INNODB STATUS` 查看锁等待情况,定位锁定问题。

  • 评论