欢迎光临
我们一直在努力

[小技巧53]一条 UPDATE 语句在 MySQL 8.0 中的完整执行旅程

一、SQL 解析与预处理(Parser & Preprocessor)

本文将以 UPDATE users SET balance = balance – 100 WHERE id = 123; 为例

当客户端发送 SQL 请求后,MySQL Server 层首先进行语法和语义分析:

  • 词法与语法解析:将 SQL 字符串转换为内部抽象语法树。若语法错误(如关键字拼写错误),在此阶段即返回错误。
  • 预处理校验:
    • 验证表 users 是否存在;
    • 检查列 balance 和 id 是否属于该表;
    • 确认当前用户是否拥有 UPDATE 权限。

此阶段不涉及存储引擎,完全由 Server 层完成。

二、查询优化(Query Optimization)

优化器基于统计信息(如索引基数、表行数)生成最优执行计划:

  • WHERE 条件分析:识别 id = 123 为等值条件。
  • 索引选择:若 id 是主键或唯一索引,则选择主键索引(聚簇索引)进行精确查找;否则可能退化为全表扫描。
  • 访问路径确定:决定使用何种方式定位目标行(如 Index Unique Scan)。
  • 执行计划缓存:若启用了 Prepared Statement 或 Query Cache(MySQL 8.0 已移除 Query Cache),可能复用已有计划。

优化结果将传递给执行器,用于后续调用存储引擎接口。

三、事务与锁机制(Transaction & Locking)

InnoDB 以事务为单位管理数据修改,并通过多版本并发控制(MVCC)与锁机制保障一致性:

  • 事务启动:若当前会话未显式开启事务,UPDATE 会隐式开启一个自动提交事务。
  • 行锁获取:
    • 若 id 为主键,InnoDB 对聚簇索引中 id=123 的记录加 X 锁(排他锁);
    • 若使用二级索引,还需回表加锁;
    • 在可重复读(REPEATABLE READ)隔离级别下,若 WHERE 条件不命中任何行,仍可能加 间隙锁(Gap Lock) 防止幻读。
  • MVCC 支持:旧版本数据通过 Undo Log 构建,供其他事务读取快照。

注:MySQL 8.0 默认隔离级别为 REPEATABLE READ。

四、InnoDB 数据修改流程

一旦锁定目标行,InnoDB 开始修改数据,涉及多个内存与日志结构:

  • Buffer Pool 查找:

    • 若目标页已在 Buffer Pool 中,直接修改内存中的页;
    • 否则,从磁盘加载数据页至 Buffer Pool(可能触发 LRU 淘汰)。
  • Change Buffer(仅适用于非唯一二级索引):

    • 本例中若 balance 有非唯一二级索引,其更新可暂存于 Change Buffer,延迟合并以减少随机 I/O。
    • 但主键更新或唯一索引不适用 Change Buffer。
  • Redo Log 写入:

    • 记录物理日志(如“将页 X 的偏移 Y 处改为 Z”),用于崩溃恢复;
    • 日志先写入 Redo Log Buffer,事务提交时根据 innodb_flush_log_at_trx_commit 配置刷盘。
  • Undo Log 生成:

    • 保存修改前的 balance 值,构成历史版本链;
    • 支持回滚与 MVCC 快照读;
    • Undo Log 本身也受 Redo 保护。
  • 五、二进制日志(Binlog)与两阶段提交(2PC)

    为支持主从复制与 PITR(时间点恢复),MySQL 启用 Binlog 时采用 两阶段提交 协调 InnoDB 与 Binlog:

  • Prepare 阶段:

    • InnoDB 将事务状态设为 PREPARE,并刷 Redo Log(含 Undo 信息);
    • 此时事务尚未对外可见。
  • Write Binlog:

    • Server 层将逻辑日志(如 UPDATE 的完整语句或行格式变更)写入 Binlog 缓冲区,并刷盘(取决于 sync_binlog)。
  • Commit 阶段:

    • InnoDB 收到 Server 层通知,将事务标记为 COMMIT,并释放行锁;
    • Redo Log 中事务状态更新为已提交。
  • 两阶段提交确保了即使在崩溃后,也能通过 Redo Log 与 Binlog 的一致性判断事务是否应重做或回滚。

    六、提交或回滚的最终处理

    • 正常提交:释放所有锁,清理事务上下文,Buffer Pool 中的脏页由后台线程(如 Page Cleaner)异步刷盘。
    • 显式/隐式回滚:利用 Undo Log 逆向恢复数据,并释放锁。
    • 崩溃恢复:启动时通过 Redo Log 重放已提交事务,通过 Undo Log 回滚未提交事务。

    执行流程图

    客户端发送 UPDATE 语句
    → Server 层:SQL 解析与预处理
    → 查询优化器生成执行计划
    → 执行器调用 InnoDB 引擎
    → InnoDB:启动事务 + 获取行锁(X 锁 / 间隙锁)
    → 定位目标行(主键索引扫描)
    → 修改 Buffer Pool 中的数据页
    → 写入 Redo Log(Prepare 状态)
    → 写入 Undo Log(用于 MVCC 与回滚)
    → Server 层写入 Binlog
    → InnoDB 提交事务(Commit 状态)
    → 释放锁,返回成功
    → 后台线程异步刷脏页至磁盘

    核心日志对比:Redo Log vs Undo Log vs Binlog

    特性Redo LogUndo LogBinlog
    所属层级 InnoDB 存储引擎 InnoDB 存储引擎 MySQL Server 层
    记录内容 物理日志(页级修改) 逻辑日志(修改前的旧值) 逻辑日志(SQL 语句或行变更)
    主要用途 崩溃恢复(保证持久性) 事务回滚 + MVCC 快照读 主从复制 + 时间点恢复
    是否循环写入 是(固定大小,循环覆盖) 否(随事务增长,由 Purge 清理) 否(按文件轮转)
    是否受 2PC 控制 是(Prepare/Commit 阶段) 是(作为 Redo 的一部分) 是(2PC 中间环节)

    七、面试题

    **面试题一:

    MySQL 的 UPDATE 语句在什么情况下会加间隙锁?**

    考察点:对 InnoDB 锁机制与隔离级别的理解。
    答案:在 REPEATABLE READ 隔离级别下,若 WHERE 条件使用非唯一索引或无索引,为防止幻读会加间隙锁。

    **面试题二:

    为什么需要两阶段提交?如果只写 Redo Log 不写 Binlog 会有什么问题?**

    考察点:对数据一致性与高可用架构的理解。
    答案:会导致主从数据不一致;崩溃恢复后主库有数据,但从库因缺少 Binlog 无法同步。

    **面试题三:

    Undo Log 存储在哪里?它会被回收吗?**

    考察点:对 MVCC 生命周期与存储管理的理解。
    答案:早期版本存于共享表空间,MySQL 5.6+ 可配置为独立 Undo 表空间;由后台 Purge 线程在无活跃事务引用时回收。

    **面试题四:

    Change Buffer 对 UPDATE 有什么影响?**

    考察点:对 I/O 优化机制的理解。
    答案:仅加速非唯一二级索引的更新;主键或唯一索引更新不受益于 Change Buffer。

    赞(0)
    未经允许不得转载:171主机测评 » [小技巧53]一条 UPDATE 语句在 MySQL 8.0 中的完整执行旅程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址