Read View是干嘛的?

1 个回答

  • 徐峰
    只有行动才能说明一切。
    它是 InnoDB 实现 **MVCC** 的核心,用来判断“**某个行版本,对当前这次一致性读取是否可见**”。 # Read View 是什么? * 一份**快照元数据**,记录“**在它创建的那一刻仍然活跃的事务集合**”及相关边界。 * 有了它,普通 `SELECT`(不加锁)就能在**不加行锁**的前提下读到**一致**的数据版本。 **Read View 主要字段(逻辑上)** * `m_ids`:创建瞬间 **仍在活跃** 的事务 ID 列表(未提交)。 * `creator_trx_id`:创建该 Read View 的事务 ID。 * `low_limit_id`:`m_ids` 中的最小值;如果 `m_ids` 为空则等于 `up_limit_id`。 * `up_limit_id`:创建瞬间**下一个将要分配**的事务 ID(相当于“高水位”)。 > 实际字段名在源码里是 `m_ids/m_low_limit_id/m_up_limit_id/m_creator_trx_id`,不同版本命名略有差异,但语义一致。 # 哪些隔离级别会用到 Read View? * **READ COMMITTED(RC)**:**每条一致性读**都会新建一个 Read View(所以同一事务内两次查到的结果可能不同)。 * **REPEATABLE READ(RR)**:**第一次一致性读**创建 Read View,**整个事务复用**这份快照(后续一致性读结果可重复)。 * **READ UNCOMMITTED**:不创建 Read View(能读到未提交版本)。 * **SERIALIZABLE**:普通 `SELECT` 会被提升为加锁读(走锁,不用 Read View)。 > 无论什么隔离级别,**当前读**(`SELECT … FOR UPDATE/LOCK IN SHARE MODE`、`UPDATE/DELETE`)都**不用** Read View,而是读取最新版本并加锁。 # 可见性判定规则(重点) 给定某行的一个版本由事务 `trx_id` 写入,针对某个 Read View 的**一致性读**是否能“看到”它? 伪代码如下(与 InnoDB 逻辑等价): ```text if (trx_id == creator_trx_id) 可见(自己改的自己能看见) else if (trx_id < low_limit_id) 可见(该版本的事务在快照创建前已提交) else if (trx_id >= up_limit_id) 不可见(该事务在快照之后才开始) else if (trx_id in m_ids) 不可见(快照时它还活跃,未提交) else 可见(介于 low 与 up 之间,但快照时它已不活跃 ⇒ 已提交) ``` 如果不可见,InnoDB 会沿着该行的 \*\*版本链(undo 日志)\*\*回退到更早版本,重复上述判定,直到找到可见版本或无可见版本。 # 一致性读 vs 当前读 * **一致性读(Consistent Read)**:普通 `SELECT`。通过 Read View 判定,**不加锁**,读到“历史可见版本”。 * **当前读(Current Read)**:`SELECT … FOR UPDATE` / `LOCK IN SHARE MODE`、`UPDATE/DELETE`。**无视 Read View**,直接读最新版本并加锁(next-key/记录锁),用于更新或防幻读。 > 在 **RR** 下:一致性读天然“可重复”;需要防“幻读”时,用**当前读**触发行间/间隙锁(next-key)来保证语义。 # RC 与 RR 的差异直观例子 ``` T1 (RR) T2 BEGIN; SELECT * FROM t WHERE id=1; -- T1 创建 Read View,看到 v0 BEGIN; UPDATE t SET c=100 WHERE id=1; COMMIT; -- v1 SELECT * FROM t WHERE id=1; -- 仍看到 v0(因为复用同一 Read View) COMMIT; ``` 同样场景在 **RC**:第二次 `SELECT` 会创建**新** Read View,于是能看到 `v1`。 # 长事务、Read View 与清理(purge) * 行的旧版本保存在 **undo** 中,**只有当所有比它更“老”的 Read View 都销毁**后,purge 线程才能真正清理这些历史版本。 * **长事务**会拖住历史版本,导致:undo/历史链变长、`history list length` 上升、二级索引回查变慢,甚至占用大量磁盘。 * 观测/诊断: * `SHOW ENGINE INNODB STATUS\G` 看 History list length * `information_schema.innodb_trx` 看长事务 * 合理控制事务时长、拆批、避免交互式长会话不提交。 # 常见面试问法与要点答案 **Q1:Read View 何时创建?** * RC:每次一致性读的语句开始时。 * RR:事务第一次一致性读时;或 `START TRANSACTION WITH CONSISTENT SNAPSHOT;` 执行当下立即创建。 **Q2:为什么 RR 能“可重复读”?** * 因为**整事务复用同一 Read View**;后续一致性读都用同一快照判定可见性。 **Q3:RR 是否靠锁避免幻读?** * 对**当前读**,靠 **next-key 锁**;对**一致性读**,读的是快照,不会“变”,但要修改满足条件的行集仍需当前读加锁来防止并发插入/删除带来的幻影。 **Q4:Read View 和快照隔离是一回事吗?** * Read View 提供的是**快照可见性**;InnoDB 的 RR 实际效果接近/强于标准的 Snapshot Isolation,但“写写冲突”由锁解决。 # 画龙点睛(如何把它讲“工程化”) * **SQL 选型**:读路径能用一致性读就别乱加 `FOR UPDATE`;真正要修改或严格防幻时再用当前读。 * **避免拖历史**:限制事务时长;大查询分批 + 每批独立事务;线上定期巡检长事务与 `history list length`。 * **理解慢查询**:版本链很长时(被长事务拖住),一致性读需要多次回退 undo,导致明显变慢,这也是实战中“读变慢”的常见根因之一。
    发布于 2025-08-14 10:00 •IP属地 湖南
    举报
    评论
    收藏