Mysql的update请求和两阶段提交

338 阅读3分钟

MySQL中的一次UPDATE操作在涉及事务提交时,特别是当启用二进制日志(binlog)以支持主从复制或恢复时,会通过 两阶段提交(Two-Phase Commit, 2PC) 机制来保证redo log(重做日志)和binlog(二进制日志)的一致性。以下是详细流程:


一、两阶段提交的背景

  • 为什么需要两阶段提交?
    当MySQL同时使用InnoDB存储引擎(依赖redo log)和二进制日志(binlog)时,需确保两者的数据一致性:
    • redo log:用于崩溃恢复,记录数据页的物理修改。
    • binlog:用于主从复制或基于时间点的恢复,记录逻辑操作。
      若两者不一致,可能导致主从数据差异或恢复错误,因此需通过2PC协调两者的写入。

二、UPDATE操作的关键步骤

以一次UPDATE操作为例,假设已开启binlog且使用InnoDB引擎:

1. 客户端发起UPDATE请求

  • 解析SQL语句,生成执行计划,定位需修改的数据行。

2. 事务开启与数据修改

  • 若未显式开启事务,自动开启一个事务(autocommit=1)。
  • 步骤
    1. 加锁:对目标行加行级排他锁(X锁)。
    2. 修改内存数据页:在Buffer Pool中找到数据页并修改,生成脏页。
    3. 写undo log:记录旧值,用于事务回滚和MVCC多版本控制。
    4. 写redo log(prepare状态) :将修改写入redo log缓冲区,标记为prepare状态(第一阶段提交)。

3. 写入binlog

  • 将UPDATE操作的逻辑记录写入binlog缓存,最终刷盘到binlog文件。

4. 提交事务(第二阶段提交)

  • 写redo log(commit状态) :将redo log中的该事务标记为commit状态,完成第二阶段提交
  • 释放锁:释放行级排他锁,事务提交完成。
  • 异步刷脏页:由后台线程将Buffer Pool中的脏页刷回磁盘数据文件(非实时)。

三、两阶段提交的详细流程

阶段1:Prepare(准备阶段)

  1. InnoDB将事务的物理修改写入redo log,并标记为prepare状态。
  2. 此时事务已锁定数据并完成内存修改,但尚未最终提交。

阶段2:Commit(提交阶段)

  1. 将事务的逻辑操作写入binlog并刷盘(确保持久化)。
  2. 将redo log中该事务的状态从prepare更新为commit
  3. 事务正式提交,释放锁资源。

四、崩溃恢复时的处理

若MySQL在提交过程中崩溃,重启后会检查redo log和binlog的一致性:

  1. Case 1:redo log为prepare且binlog完整
    • 说明binlog已写入,但redo log未提交。
    • 恢复操作:提交事务(将数据修改生效)。
  1. Case 2:redo log为prepare但binlog不完整
    • 说明第二阶段未完成。
    • 恢复操作:回滚事务(利用undo log撤销修改)。

通过这种机制,确保redo log和binlog的数据严格一致


五、两阶段提交的意义

  1. 原子性保证
    • 即使崩溃,也能通过日志恢复,保证事务的“全做或全不做”。
  1. 主从数据一致性
    • 确保主库的binlog和从库的relay log完全一致。
  1. 高性能与安全的平衡
    • 通过异步刷盘和日志顺序写,减少磁盘I/O次数,兼顾性能与安全。

六、流程图解

UPDATE操作流程:
1. 客户端发送UPDATE请求
   │
   ├─ 加行锁(X锁)
   ├─ 修改Buffer Pool数据页 → 生成脏页
   ├─ 写undo log(用于回滚/MVCC)
   └─ 写redo log(prepare状态) → 阶段1完成
   │
   ├─ 写binlog → 持久化逻辑操作
   │
   └─ 写redo log(commit状态) → 阶段2完成
      │
      ├─ 释放行锁
      └─ 后台刷脏页到磁盘

七、总结

  • 两阶段提交是MySQL协调redo log(物理日志)和binlog(逻辑日志)的核心机制,确保事务的原子性和数据一致性。
  • prepare阶段保证物理修改可追溯,commit阶段保证逻辑操作持久化。
  • 崩溃恢复时通过对比redo log和binlog状态,自动修复不一致问题。