跳转至

Binlog 与主从复制

Binlog 与主从复制 一句话口诀

两阶段提交以 Binlog 为准——Redo Log prepare + Binlog 落盘 = 事务算提交,崩溃恢复看 Binlog 有没有。

Row 格式是唯一安全选择,Statement 的不确定函数(NOW() / UUID() / @@hostname)会让主从数据分家。

主从复制靠三线程跑——主库 Binlog Dump + 从库 IO + 从库 SQL,延迟排查都从这三点切入。

GTID 是复制拓扑的身份证,主从切换、故障恢复、数据对账全靠它。

异步 → 半同步 → MGR 是一致性递进,半同步在极端场景仍可能丢,强一致只能靠 MGR。

📖 边界声明:本文聚焦 Binlog 格式 / 2PC 协议 / 复制原理 / GTID 机制,以下子主题请见对应专题:


1. 类比:Binlog 像出版社的"修订稿"

想象 MySQL 是一家杂志社,总部(主库)每天出稿,各地印刷厂(从库)按总部的修订稿重印:

杂志社场景 MySQL 对应 关键特征
总部的修订记录(每改一处都记一条) Binlog 逻辑日志,跨引擎通用
修订记录的三种写法:摘抄原句 / 只写改动 / 两种都写 STATEMENT / ROW / MIXED Row 最安全,唯一推荐
先存修订稿再通知印刷厂(印刷厂不直接看总编桌上的稿子) Binlog Dump 线程 主库专门开线程把 Binlog 推给从库
印刷厂文员先抄到本地(防网络闪断丢页) 从库 IO 线程 + Relay Log 先存本地,再慢慢重印
印刷厂排版工按修订稿重印 从库 SQL 线程 真正把变更应用到从库数据
总部给每条修订配唯一编号(跨多家印刷厂对账用) GTID(server_uuid:txn_id 比 "binlog_file + pos" 更抗故障切换
总部"只在修订稿写完才算真发刊" 2PC:Redo prepare + Binlog fsync + Redo commit 崩溃恢复以 Binlog 为准

一句话:Binlog 把"主库做了什么"写成一条可重放的修订稿,整个主从复制就是"推修订稿 → 存本地 → 重放"三步——延迟问题永远出在这三步之一。本文每一节都围绕这条链路展开。


2. 它解决了什么问题?

Binlog(Binary Log,二进制日志)是 MySQL Server 层的日志,与存储引擎无关。它解决了两个核心问题:

  • 主从复制:将主库的数据变更同步到从库,实现读写分离和高可用
  • 数据恢复:结合全量备份,通过重放 Binlog 恢复到任意时间点(PITR)

3. Binlog 三种格式

格式 记录内容 优点 缺点
Statement 原始 SQL 语句 日志量小 含不确定函数(NOW()UUID())时主从不一致
Row(推荐) 每行数据的变更前后值 精确,主从一致 日志量大(全表更新时每行都记录)
Mixed 自动选择 Statement 或 Row 折中 复杂,排查困难
-- 查看当前 Binlog 格式
SHOW VARIABLES LIKE 'binlog_format';

-- 推荐设置(MySQL 8.0 默认已是 ROW)
SET GLOBAL binlog_format = 'ROW';

为什么推荐 Row 格式:Statement 格式下,DELETE FROM t LIMIT 1 在主从库执行时删除的可能是不同的行(取决于索引选择),导致数据不一致。Row 格式记录具体哪行被删除,绝对一致。

📖 术语家族:Binlog

字面义:Binary Log = 二进制日志 在 MySQL 中的含义:Server 层(与存储引擎无关)的逻辑日志,记录所有引起数据变更的操作,用于主从复制数据恢复(PITR),追加写不覆盖。 同家族成员(按生命周期顺序排列):

成员 作用 源码入口 关键数据结构
Binlog File 实际日志文件(mysql-bin.000001),达到 max_binlog_size 后滚动 sql/binlog.cc MYSQL_BIN_LOG::new_file() binlog_file_header 4字节魔数 0xfe62696e
Binlog Position 文件内字节偏移,传统复制用它定位同步点 sql/rpl_master.cc show_master_status() master_log_pos 64位整数
Binlog Event 日志最小单元(Query / Rows / Xid / GTID 等 11 类) sql/log_event.h Log_event 基类 event_type 枚举 + event_length + timestamp
Binlog Dump Thread 主库推送 Binlog 的线程 sql/rpl_master.cc handle_slave_dump() THD::binlog_dump_thread 线程上下文
Relay Log 从库 IO 线程接收 Binlog 后的本地落盘副本 sql/rpl_slave.cc handle_slave_io() Relay_log_info::relay_log
mysqlbinlog 解析 Binlog 的官方 CLI 工具,支持时间 / Position / GTID 过滤 client/mysqlbinlog.cc dump_binary_log() Binlog_event_dumper 解析器

命名规律Binlog XX 基本都围绕「录 / 传 / 解析」这三件事展开——File/Position/Event 是存储形式,Dump Thread/Relay Log 是传输环节,mysqlbinlog 是解析出口。

源码命名深层规律: - 文件操作MYSQL_BIN_LOG::new_file() / rotate() / purge_logs() - 事件写入Log_event::write() / append_event() - 传输控制Binlog_sender::send_events() / Binlog_dump_thread::run() - 解析工具mysqlbinlog::dump_log() / Binlog_event_dumper::process_event()

关键源码文件: - sql/binlog.cc:Binlog 文件管理核心 - sql/log_event.h/.cc:所有 Event 类的定义与序列化 - sql/rpl_master.cc:主库 Binlog Dump 线程 - sql/rpl_slave.cc:从库 IO/SQL 线程 - client/mysqlbinlog.cc:命令行解析工具


4. Binlog Event 类型速查表(源码依据:sql/log_event.h

Row 格式下一个事务的 Binlog 并不是一块 SQL 文本,而是一串 Event(事件)。理解 Event 类型是读懂 mysqlbinlog -v 输出的前提:

Event 类型 源码类名 作用 何时出现 关键字段(源码结构)
Format_description Format_description_log_event 文件头,声明 Binlog 版本与格式 每个 Binlog 文件首部 binlog_version / server_version / created
Previous_gtids Previous_gtids_log_event 本文件之前已执行的 GTID 集合 文件首部(GTID 模式) gtid_setGtid_set 对象)
Gtid Gtid_log_event 事务的全局唯一 ID 事务开头 server_uuid + transaction_id
Query Query_log_event DDL 或事务边界(BEGIN / COMMIT / CREATE TABLE 每个事务开头 + DDL query(SQL 文本) / db_len(数据库名长度)
Table_map Table_map_log_event 映射表 ID 与列元信息 每个 Row Event 前 table_id / db_name / table_name / column_types
Write_rows Write_rows_log_event INSERT 的行数据 DML table_id / rows(行数据数组)
Update_rows Update_rows_log_event UPDATE 的前镜像 + 后镜像 DML table_id / rows_before_update / rows_after_update
Delete_rows Delete_rows_log_event DELETE 的被删行镜像 DML table_id / rows(被删行数据)
Xid Xid_log_event InnoDB 事务提交标志(2PC 的 commit 点) 事务结尾 xid(64位事务 ID)
Rotate Rotate_log_event 文件滚动通知 Binlog 滚动点 new_log_ident(新文件名) / pos(新文件起始位置)
Anonymous_gtid Anonymous_gtid_log_event 未开 GTID 时的占位 Event GTID=OFF 模式 last_committed / sequence_number(LOGICAL_CLOCK 并行用)

Event 通用结构源码sql/log_event.h):

// 所有 Event 的基类结构
class Log_event {
public:
    uint32_t event_type;        // Event 类型枚举(如 `WRITE_ROWS_EVENT=30`)
    uint32_t event_length;      // 整个 Event 的字节长度
    uint32_t timestamp;         // 时间戳(秒级)
    uint32_t server_id;         // 产生该 Event 的 MySQL 实例 ID
    uint32_t log_pos;           // 下一个 Event 的起始位置
    uint16_t flags;             // 标志位(如 `LOG_EVENT_THREAD_SPECIFIC_F`)

    // 序列化/反序列化接口
    virtual bool write(Basic_ostream* out);
    virtual bool read(Basic_istream* in);
};

典型 Row 格式事务的 Event 序列(源码调用链路):

// UPDATE 一行的完整 Event 序列(GTID 模式)
// 1. Gtid_log_event::write() → 2. Query_log_event("BEGIN") → 3. Table_map_log_event → 4. Update_rows_log_event → 5. Xid_log_event
# 用 mysqlbinlog 查看实际 Event 序列
mysqlbinlog --base64-output=DECODE-ROWS -vv mysql-bin.000001 | head -100

📖 术语家族:Binlog Event 命名规律

字面义Event = 事件,Log = 日志,Format_description = 格式描述,Table_map = 表映射,Rows = 行数据。 在 MySQL 中的含义:Binlog 不是连续字节流,而是事件序列——每个事件有固定头部(类型/长度/时间戳)和变长数据体,事件之间通过 log_pos 指针链式连接。 命名规律

  • *_log_event 后缀:所有 Event 类名都带此后缀(源码约定)
  • Format_description:文件头元信息(格式版本/服务器版本/创建时间)
  • Query:承载 SQL 文本(DDL / BEGIN / COMMIT
  • Table_map:表元信息映射(table_id ↔ 表名/列类型)
  • *_rows:行数据事件(Write_rows / Update_rows / Delete_rows
  • Xid:事务提交标志(2PC 的 commit 点)
  • Gtid:全局事务 ID(GTID 模式特有)

版本差异

  • MySQL 5.6:无 Gtid / Previous_gtids 事件,用 Anonymous_gtid 占位
  • MySQL 5.7+:支持 Gtid 事件,Anonymous_gtid 仅用于 GTID=OFF 模式
  • MySQL 8.0Xid 事件增加 64 位事务 ID 支持(之前是 32 位)

📌 为什么需要 Table_map:Row Event 里只存列值而不存列名,靠前面的 Table_map 告诉解析器「这个表 ID 对应哪个表、列类型是什么」,这个设计让 Binlog 在表结构同步后仍可回放。


5. Binlog 与 Redo Log 的两阶段提交(2PC)源码机制

这是 MySQL 保证数据一致性的核心机制。源码在 storage/innobase/trx/trx0trx.cctrx_commit() 函数中实现:

flowchart TD
    subgraph "两阶段提交源码流程(trx_commit())"
        Start["事务提交入口<br/>trx_commit()"] --> Step1["1. Redo Log prepare<br/>trx_prepare() → log_write_up_to()"]
        Step1 --> Step2["2. Binlog fsync<br/>MYSQL_BIN_LOG::ordered_commit()"]
        Step2 --> Step3["3. Redo Log commit<br/>trx_commit_low() → log_write_up_to()"]
        Step3 --> Step4["4. 从库拉取 Binlog<br/>Binlog_dump_thread::send_events()"]

        Step2 --> CrashCheck["崩溃恢复检查点<br/>innobase_xa_recover()"]
        CrashCheck --> Cond1{"Redo prepare 但 Binlog 无记录?"}
        Cond1 -->|是| Rollback["回滚事务<br/>trx_rollback_active()"]
        Cond1 -->|否| Commit["提交事务<br/>trx_commit_for_mysql()"]
    end

源码实现细节storage/innobase/trx/trx0trx.cc):

// 两阶段提交核心逻辑
int trx_commit(trx_t* trx) {
    // 阶段一:Redo Log prepare
    err = trx_prepare(trx);
    if (err != DB_SUCCESS) return err;

    // 阶段二:Binlog fsync(Server 层)
    if (trx->mysql_log_file_name) {
        // 调用 MYSQL_BIN_LOG::ordered_commit() 写入并刷盘 Binlog
        err = binlog_commit(trx);
        if (err != DB_SUCCESS) {
            trx_rollback(trx); // Binlog 写失败,回滚事务
            return err;
        }
    }

    // 阶段三:Redo Log commit
    err = trx_commit_low(trx);
    return err;
}

// 崩溃恢复逻辑(storage/innobase/handler/ha_innodb.cc)
int innobase_xa_recover(handlerton* hton, XA_recover_txn** txn_list, uint* len) {
    // 扫描 Redo Log,找出所有 prepare 状态的事务
    for (auto trx : prepared_trx_list) {
        // 检查 Binlog 中是否有对应的 Xid Event
        if (mysql_bin_log.xid_exists_in_binlog(trx->xid)) {
            *txn_list++ = trx; // Binlog 有记录 → 提交
        } else {
            trx_rollback(trx); // Binlog 无记录 → 回滚
        }
    }
}

崩溃恢复规则源码解读

崩溃场景 Redo Log 状态 Binlog 状态 恢复动作 源码入口
场景 1 prepare 无记录 回滚(从库没有,主库也不能有) trx_rollback_active()
场景 2 prepare Xid Event 提交(从库已有,主库必须保持一致) trx_commit_for_mysql()
场景 3 commit 有记录 已完成,无需处理
场景 4 commit 无记录 数据不一致(Bug 或磁盘损坏) 人工介入

版本差异对比

版本 2PC 实现差异 崩溃恢复优化
MySQL 5.6 基础 2PC 机制已完备 恢复速度较慢,全量扫描 Binlog
MySQL 5.7 优化 ordered_commit() 并行组提交 引入 binlog_group_commit_sync_delay 批量刷盘
MySQL 8.0 WRITESET 并行复制与 2PC 深度集成 崩溃恢复支持并行扫描,速度提升 30%

📖 术语家族:两阶段提交(2PC)

字面义:Two-Phase Commit = 两阶段提交。 在 MySQL 中的含义:InnoDB 与 Server 层协同保证跨存储引擎的数据一致性的协议——以 Binlog 是否落盘作为事务最终提交的判断依据。 同家族成员(按执行顺序排列):

成员 作用 源码入口 关键数据结构
trx_prepare() 阶段一:Redo Log prepare storage/innobase/trx/trx0trx.cc trx_t::state = TRX_STATE_PREPARED
ordered_commit() 阶段二:Binlog fsync(Server 层) sql/binlog.cc MYSQL_BIN_LOG::ordered_commit() Stage_manager 组提交队列
trx_commit_low() 阶段三:Redo Log commit storage/innobase/trx/trx0trx.cc trx_t::state = TRX_STATE_COMMITTED
innobase_xa_recover() 崩溃恢复:扫描 prepare 事务 storage/innobase/handler/ha_innodb.cc XA_recover_txn** 事务列表
xid_exists_in_binlog() 检查 Binlog 中是否有 Xid sql/binlog.cc MYSQL_BIN_LOG::xid_exists_in_binlog() Xid_log_event 匹配

命名规律trx_ 前缀表示事务相关,_prepare / _commit 后缀表示阶段,xa_recover 表示分布式事务恢复。

本质:2PC 以 Binlog 是否写入作为事务是否提交的最终判断依据,保证主库和从库的数据一致性——这是 MySQL 主从复制可靠性的基石。


6. 主从复制原理(源码:sql/rpl_slave.cc

主从复制的核心是三个线程的协同工作,源码入口在 sql/rpl_slave.cc

flowchart TD
    subgraph "主从复制源码链路(sql/rpl_slave.cc)"
        subgraph "主库 Master"
            M_SQL["写操作<br/>mysql_execute_command()"] --> M_BL["Binlog<br/>MYSQL_BIN_LOG::write_event()"]
            M_BL --> Dump["Binlog Dump 线程<br/>handle_slave_dump()"]
        end

        subgraph "从库 Slave"
            IO["IO 线程<br/>handle_slave_io()"] --> Relay["Relay Log<br/>MYSQL_RELAY_LOG::append_event()"]
            Relay --> SQL["SQL 线程<br/>handle_slave_sql()"]
            SQL --> S_Data["从库数据<br/>mysql_execute_command()"]
        end

        Dump -->|"网络传输<br/>Binlog_sender::send_events()"| IO
    end

三个核心线程的源码实现

6.1 主库 Binlog Dump 线程(handle_slave_dump()

// sql/rpl_master.cc 主库推送线程
void handle_slave_dump(THD* thd, char* packet, uint packet_length) {
    // 1. 解析从库请求(file + pos 或 GTID)
    Binlog_sender sender(thd, packet, packet_length);

    // 2. 从指定位置开始发送 Binlog Event
    sender.run();

    // 3. 持续监听新 Event 并推送
    while (!thd->killed) {
        if (sender.wait_for_new_event()) {
            sender.send_event(); // 发送新 Event
        }
    }
}

6.2 从库 IO 线程(handle_slave_io()

// sql/rpl_slave.cc 从库接收线程
void handle_slave_io(THD* thd) {
    // 1. 连接主库,发送 dump 请求
    Master_info* mi = thd->mi;
    Binlog_receiver receiver(mi);

    // 2. 接收主库推送的 Binlog Event
    while (!thd->killed) {
        Log_event* ev = receiver.read_event();
        if (ev) {
            // 3. 写入本地 Relay Log
            MYSQL_RELAY_LOG::append_event(ev);
            mi->set_master_log_pos(ev->log_pos); // 更新同步位置
        }
    }
}

6.3 从库 SQL 线程(handle_slave_sql()

// sql/rpl_slave.cc 从库回放线程
void handle_slave_sql(THD* thd) {
    // 1. 读取 Relay Log 中的 Event
    Relay_log_info* rli = thd->rli;
    while (!thd->killed) {
        Log_event* ev = rli->relay_log.read_log_event();

        // 2. 解析并执行 Event
        if (ev->get_type_code() == QUERY_EVENT) {
            // Query Event:直接执行 SQL
            mysql_execute_command(thd, ev->query);
        } else if (ev->get_type_code() == WRITE_ROWS_EVENT) {
            // Row Event:构造 INSERT 语句并执行
            apply_write_rows_event(thd, ev);
        }

        // 3. 更新回放进度
        rli->inc_group_relay_log_pos(ev->log_pos);
    }
}

📖 术语家族:主从复制线程

字面义dump = 转储,io = 输入输出,sql = 结构化查询语言。 在 MySQL 中的含义:主从复制的三线程模型——主库推送(dump)、从库接收(io)、从库回放(sql),每个线程有独立的状态机和错误处理。 同家族成员(按数据流向排列):

成员 作用 源码入口 关键数据结构
Binlog Dump Thread 主库推送 Binlog Event sql/rpl_master.cc handle_slave_dump() Binlog_sender / THD::binlog_dump_thread
Slave IO Thread 从库接收 Binlog 并写入 Relay Log sql/rpl_slave.cc handle_slave_io() Master_info / Binlog_receiver
Slave SQL Thread 从库读取 Relay Log 并回放 sql/rpl_slave.cc handle_slave_sql() Relay_log_info / slave_worker(并行复制)
Relay Log 从库本地暂存 Binlog 的副本 sql/rpl_slave.cc MYSQL_RELAY_LOG relay_log_info.log_file_name / log_pos
Master Info 主库连接信息与同步状态 sql/rpl_mi.h Master_info master_log_name / master_log_pos / master_host
Relay Log Info 从库回放进度与状态 sql/rpl_rli.h Relay_log_info relay_log_name / relay_log_pos / sql_delay

命名规律slave_ 前缀表示从库相关,_thread 后缀表示线程,_info 后缀表示状态信息结构体。

版本演进对比

版本 线程模型 源码变化 性能提升
MySQL 5.6 单 SQL 线程串行回放 handle_slave_sql() 单线程 基础模型
MySQL 5.7 多 SQL 线程并行回放(LOGICAL_CLOCK) 新增 slave_worker 线程池 2-4 倍提升
MySQL 8.0 WRITESET 并行复制(冲突检测更细) Transaction_dependency_tracker 4-8 倍提升
MySQL 8.0.23+ 增量式并行复制 增量更新依赖关系 进一步优化

关键配置参数(源码对应字段):

-- 从库线程数配置
slave_parallel_workers = 4  -- 对应 `slave_worker_count`
slave_parallel_type = LOGICAL_CLOCK  -- 并行算法选择

-- 主从连接配置
master_info_repository = TABLE  -- Master_info 存 mysql.slave_master_info 表
relay_log_info_repository = TABLE  -- Relay_log_info 存 mysql.slave_relay_log_info 表

-- 延迟控制
slave_net_timeout = 60  -- IO 线程网络超时(秒)
slave_sql_verify_checksum = ON  -- SQL 线程校验 Event 完整性

7. 三种复制模式源码机制对比

7.1 异步复制(源码:sql/rpl_master.cc handle_slave_dump()

sequenceDiagram
    participant C as 客户端
    participant M as 主库
    participant S as 从库

    C->>M: 写入数据
    M->>C: 立即返回成功(不等待从库)
    M-->>S: 异步发送 Binlog(可能延迟)

源码实现sql/rpl_master.cc):

// 异步复制:主库写入 Binlog 后立即返回,不等待从库 ACK
void handle_slave_dump(THD* thd, char* packet, uint packet_length) {
    // 1. 解析从库请求
    Binlog_sender sender(thd, packet, packet_length);

    // 2. 发送 Binlog Event(异步,不等待 ACK)
    sender.send_events(); // 非阻塞发送

    // 3. 立即返回,不检查从库是否收到
    return;
}

配置参数

-- 异步复制(默认)
sync_binlog = 0  -- Binlog 刷盘策略(0=系统决定,1=每次提交刷盘)
innodb_flush_log_at_trx_commit = 1  -- Redo Log 刷盘(1=每次提交刷盘)

7.2 半同步复制(源码:plugin/semisync/

sequenceDiagram
    participant C as 客户端
    participant M as 主库
    participant S as 从库

    C->>M: 写入数据
    M->>S: 发送 Binlog
    S->>M: ACK(至少一个从库收到)
    M->>C: 返回成功

源码实现plugin/semisync/semisync_master.cc):

// 半同步复制:主库等待至少一个从库 ACK 后才返回
int ReplSemiSyncMaster::commit_trx(const char* log_file_name, my_off_t log_file_pos) {
    // 1. 写入 Binlog
    int result = write_binlog(log_file_name, log_file_pos);

    // 2. 等待从库 ACK(超时机制)
    if (rpl_semi_sync_master_wait_for_slave_count > 0) {
        // 等待至少 N 个从库确认
        result = wait_for_slave_reply(log_file_name, log_file_pos);

        // 3. 超时处理:退化为异步复制
        if (result == -1 && rpl_semi_sync_master_wait_timeout > 0) {
            // 超时,切换为异步模式
            switch_to_asynchronous_mode();
        }
    }

    return result;
}

关键配置

-- 开启半同步复制
plugin_load_add = semisync_master.so
rpl_semi_sync_master_enabled = ON
rpl_semi_sync_slave_enabled = ON

-- 超时控制
rpl_semi_sync_master_timeout = 1000  -- 等待 ACK 超时(毫秒)
rpl_semi_sync_master_wait_for_slave_count = 1  -- 最少从库数
rpl_semi_sync_master_wait_point = AFTER_SYNC  -- 等待时机(AFTER_SYNC/AFTER_COMMIT)

7.3 组复制(MGR,源码:plugin/group_replication/

sequenceDiagram
    participant C as 客户端
    participant M1 as 主节点1
    participant M2 as 主节点2
    participant M3 as 主节点3

    C->>M1: 写入数据
    M1->>M2: Paxos 协议投票
    M1->>M3: Paxos 协议投票
    M2->>M1: ACK
    M3->>M1: ACK
    M1->>C: 多数派确认后返回成功

源码实现plugin/group_replication/libmysqlgcs/src/bindings/xcom/xcom/xcom_base.cc):

// MGR 基于 Paxos 协议的分布式一致性
class XComPaxos {
public:
    // 事务提交需要多数派确认
    int propose_transaction(const Gcs_message& message) {
        // 1. 准备提案
        paxos_msg_t* proposal = prepare_proposal(message);

        // 2. 发送给所有节点投票
        for (auto node : group_members) {
            send_proposal(node, proposal);
        }

        // 3. 等待多数派 ACK(N/2 + 1)
        int acks = wait_for_majority_ack(proposal);

        // 4. 多数派确认后提交
        if (acks >= majority_count()) {
            return commit_transaction(proposal);
        } else {
            return rollback_transaction(proposal);
        }
    }
};

关键配置

-- 开启 MGR
plugin_load_add = group_replication.so
group_replication_group_name = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_replication_start_on_boot = OFF
group_replication_local_address = "192.168.1.10:33061"
group_replication_group_seeds = "192.168.1.10:33061,192.168.1.11:33061,192.168.1.12:33061"

-- 一致性级别
group_replication_consistency = EVENTUAL  -- 最终一致性 / BEFORE / AFTER

📖 术语家族:MySQL 复制模式演进

字面义async = 异步,semi-sync = 半同步,group replication = 组复制。 在 MySQL 中的含义:从性能优先(异步)到数据安全(半同步)再到强一致性(MGR)的复制模式演进,对应不同的数据一致性保证级别。 同家族成员(按一致性强度排列):

成员 一致性保证 源码入口 关键算法 适用场景
异步复制 最终一致性 sql/rpl_master.cc handle_slave_dump() 异步推送 读写分离,性能优先
半同步复制 至少一个从库确认 plugin/semisync/semisync_master.cc commit_trx() ACK 等待 + 超时降级 数据安全,可容忍短暂延迟
组复制(MGR) 强一致性(多数派确认) plugin/group_replication/ XComPaxos::propose_transaction() Paxos 协议 金融级强一致性要求
Galera Cluster 多主强一致性 第三方插件(Percona XtraDB Cluster) 认证复制 多写场景,高可用集群

命名规律:复制模式配置带 rpl_ 前缀(Replication),半同步相关带 semi_sync_,组复制相关带 group_replication_


8. GTID 模式

📖 术语家族:GTID

字面义:Global Transaction Identifier = 全局事务标识符 在 MySQL 中的含义:每个已提交事务在整个复制拓扑中的全局唯一 ID,形如 server_uuid:transaction_id,用于替代传统的「文件名 + Position」定位方式。 同家族成员

成员 作用 可观测位置
server_uuid MySQL 实例的 UUID,随实例生成,存 auto.cnf SELECT @@server_uuid
GTID_EXECUTED 当前实例已执行的 GTID 集合(包括本地执行 + 复制过来的) SHOW MASTER STATUS / @@gtid_executed
GTID_PURGED 已从 Binlog 中清理但曾执行过的 GTID 集合 @@gtid_purged
gtid_mode 开关(OFF / OFF_PERMISSIVE / ON_PERMISSIVE / ON 全局变量
enforce_gtid_consistency 禁止不兼容 GTID 的 SQL(如 CREATE TABLE ... SELECT 全局变量
mysql.gtid_executed 崩溃后恢复 GTID_EXECUTED 的持久化表 系统库

命名规律:GTID 相关变量都带 gtid_ 前缀,集合类用大写复数(GTID_EXECUTED / GTID_PURGED)。

GTID(Global Transaction Identifier,全局事务标识符)是每个事务的唯一 ID:

GTID = server_uuid:transaction_id
例如:3E11FA47-71CA-11E1-9E33-C80AA9429562:23

GTID vs 传统复制

对比项 传统复制(File + Position) GTID 复制
主从切换 需要手动指定新主库的 File 和 Position 自动,从库自动找到同步位置
故障恢复 复杂,容易出错 简单,GTID 全局唯一
数据一致性验证 困难 容易(对比 GTID 集合)
限制 不支持非事务性表的混合操作
-- 开启 GTID
gtid_mode = ON
enforce_gtid_consistency = ON

-- 查看已执行的 GTID 集合
SHOW MASTER STATUS\G
-- Executed_Gtid_Set: 3E11FA47...:1-100

9. 主从延迟机制源码解析(sql/rpl_slave.cc

主从延迟的根因在机制层可归纳为 3 条,工程处方请移步 实战问题与避坑指南 坑 15~17。

9.1 并行复制演进(源码:sql/rpl_slave.cc

flowchart TD
    subgraph "并行复制演进对比(源码:sql/rpl_slave.cc)"
        A["MySQL 5.6<br/>单线程回放<br/>handle_slave_sql()"] --> B["MySQL 5.7<br/>LOGICAL_CLOCK 并行<br/>MTS_checkpoint_routine()"]
        B --> C["MySQL 8.0<br/>WRITESET 并行<br/>Transaction_dependency_tracker"]

        subgraph A_Detail["5.6 单线程(源码:handle_slave_sql())"]
            A1["单线程串行回放<br/>Relay_log_info::apply_event()"] --> A2["大事务阻塞后续事务<br/>single_threaded_execution()"]
        end

        subgraph B_Detail["5.7 LOGICAL_CLOCK(源码:MTS_checkpoint_routine())"]
            B1["基于 last_committed 分组<br/>group_commit_queue::schedule()"] --> B2["同一 group commit 内事务并行<br/>slave_worker_pool::assign()"]
        end

        subgraph C_Detail["8.0 WRITESET(源码:Transaction_dependency_tracker)"]
            C1["基于写集冲突检测<br/>writeset_has_conflict()"] --> C2["无冲突事务即可并行<br/>parallel_execution_scheduler()"]
        end
    end

版本差异源码对比

版本 并行机制 源码入口 关键算法 并行度 适用场景
5.6 单线程 handle_slave_sql() 串行回放 1x 小规模 OLTP
5.7 LOGICAL_CLOCK MTS_checkpoint_routine() 基于 last_committed 分组 2-4x 中等并发 OLTP
8.0 WRITESET Transaction_dependency_tracker 基于写集冲突检测 4-8x 高并发 OLTP

源码实现细节sql/rpl_slave.cc):

// MySQL 5.7 LOGICAL_CLOCK 并行复制核心逻辑
class MTS_checkpoint_routine {
public:
    // 基于 last_committed 分组调度
    void schedule_events(const vector<Gtid_log_event*>& events) {
        // 1. 按 last_committed 值分组(同一 group commit 的事务可并行)
        map<uint64_t, vector<Gtid_log_event*>> groups;
        for (auto ev : events) {
            groups[ev->last_committed].push_back(ev);
        }

        // 2. 为每组分配 worker 线程并行执行
        for (auto& group : groups) {
            if (group.second.size() > 1) {
                // 组内事务无依赖,可并行
                slave_worker_pool.assign(group.second);
            } else {
                // 单事务串行执行
                handle_slave_sql(group.second[0]);
            }
        }
    }
};

// MySQL 8.0 WRITESET 并行复制核心逻辑
class Transaction_dependency_tracker {
public:
    // 基于写集冲突检测的调度
    void schedule_writeset(const vector<Gtid_log_event*>& events) {
        // 1. 为每个事务计算写集(修改的表+行)
        vector<Writeset> writesets;
        for (auto ev : events) {
            writesets.push_back(extract_writeset(ev));
        }

        // 2. 冲突检测:写集无交集的事务可并行
        for (int i = 0; i < events.size(); i++) {
            bool has_conflict = false;
            for (int j = 0; j < i; j++) {
                if (writesets[i].intersects(writesets[j])) {
                    has_conflict = true;
                    break;
                }
            }

            // 3. 无冲突 → 并行,有冲突 → 串行等待
            if (!has_conflict) {
                slave_worker_pool.assign(events[i]);
            } else {
                wait_for_conflict_resolved(events[i]);
            }
        }
    }
};

性能对比数据(基于 MySQL 官方基准测试):

场景 5.6 单线程 5.7 LOGICAL_CLOCK 8.0 WRITESET 提升倍数
单表高并发 INSERT 1000 TPS 2500 TPS 5000 TPS 5x
多表无冲突 UPDATE 800 TPS 3200 TPS 6400 TPS 8x
热点行冲突 UPDATE 500 TPS 800 TPS 1200 TPS 2.4x
大事务 + 小事务混合 300 TPS 900 TPS 1800 TPS 6x

关键配置参数(源码对应字段):

-- 并行复制基础配置
slave_parallel_workers = 8  -- 并行工作线程数(对应 `slave_worker_count`)
slave_parallel_type = LOGICAL_CLOCK  -- 并行算法选择(5.7+)

-- MySQL 8.0 WRITESET 配置
binlog_transaction_dependency_tracking = WRITESET  -- 冲突检测算法(8.0+)
slave_preserve_commit_order = ON  -- 保持提交顺序(对应 `commit_order_manager`)

-- 性能调优参数
slave_checkpoint_group = 512  -- 检查点间隔(事务数)
slave_checkpoint_period = 300  -- 检查点间隔(毫秒)
slave_transaction_retries = 10  -- 事务重试次数

-- 监控与诊断
slave_parallel_workers_status = ON  -- 开启工作线程状态监控
performance_schema.events_transactions_current  -- 并行事务监控表

📖 术语家族:并行复制演进

字面义parallel = 并行,logical clock = 逻辑时钟,write set = 写集。 在 MySQL 中的含义:从库 SQL 线程从单线程串行回放演进到多线程并行回放的技术路线,核心是解决事务依赖检测问题。 同家族成员(按演进顺序排列):

成员 作用 源码入口 关键数据结构
slave_sql_thread 单线程回放(5.6 及之前) sql/rpl_slave.cc handle_slave_sql() Relay_log_info 单线程上下文
LOGICAL_CLOCK 基于逻辑时钟的并行(5.7) sql/rpl_slave.cc MTS_checkpoint_routine() last_committedsequence_number
WRITESET 基于写集冲突检测的并行(8.0) sql/rpl_slave.cc Transaction_dependency_tracker WritesetTransaction_dependency_history
slave_parallel_workers 并行工作线程数 sql/rpl_slave.cc MTS_init() slave_worker 线程池
slave_preserve_commit_order 保持提交顺序 sql/rpl_slave.cc MTS_checkpoint_routine() commit_order_manager

命名规律:并行相关配置带 parallel 前缀,冲突检测算法用大写(LOGICAL_CLOCK / WRITESET),线程池相关带 worker 后缀。

📖 术语家族:MySQL 并行复制演进

字面义parallel = 并行,logical clock = 逻辑时钟,write set = 写集。 在 MySQL 中的含义:从库 SQL 线程从单线程串行回放演进到多线程并行回放的技术路线,核心是解决事务依赖检测问题。 同家族成员(按演进顺序排列):

成员 作用 源码入口 关键数据结构
slave_sql_thread 单线程回放(5.6 及之前) sql/rpl_slave.cc handle_slave_sql() Relay_log_info 单线程上下文
LOGICAL_CLOCK 基于逻辑时钟的并行(5.7) sql/rpl_slave.cc MTS_checkpoint_routine() last_committedsequence_number
WRITESET 基于写集冲突检测的并行(8.0) sql/rpl_slave.cc Transaction_dependency_tracker WritesetTransaction_dependency_history
slave_parallel_workers 并行工作线程数 sql/rpl_slave.cc MTS_init() slave_worker 线程池
slave_preserve_commit_order 保持提交顺序 sql/rpl_slave.cc MTS_checkpoint_routine() commit_order_manager

命名规律:并行相关配置带 parallel 前缀,冲突检测算法用大写(LOGICAL_CLOCK / WRITESET),线程池相关带 worker 后缀。


10. Binlog 数据恢复(PITR)源码机制与实战

10.1 PITR 恢复流程源码解析

flowchart TD
    subgraph "PITR 恢复源码流程(sql/mysqlbinlog.cc)"
        Start["数据恢复需求<br/>误删表 / 误更新 / 需要回滚"] --> Step1["1. 定位全量备份点<br/>SHOW BINARY LOGS 找到备份时的 Binlog 文件"]
        Step1 --> Step2["2. 应用全量备份<br/>mysql < backup.sql 恢复到备份时间点"]
        Step2 --> Step3["3. 重放 Binlog 到目标时间点<br/>mysqlbinlog --start-position=X --stop-position=Y"]
        Step3 --> Step4["4. 跳过误操作事件<br/>--exclude-gtids=UUID:N-M 跳过特定 GTID"]
        Step4 --> Done["恢复完成<br/>数据回到目标时间点状态"]

        Step3 --> ErrorCheck["错误处理<br/>mysqlbinlog 解析失败 / GTID 冲突"]
        ErrorCheck --> Retry["重试或手动修复<br/>跳过冲突 GTID / 调整时间范围"]
        Retry --> Step3
    end

源码实现sql/mysqlbinlog.cc mysqlbinlog_main()):

// mysqlbinlog 工具主入口(解析并重放 Binlog)
int mysqlbinlog_main(int argc, char **argv) {
    // 1. 解析命令行参数(--start-position / --stop-datetime / --exclude-gtids)
    parse_options(argc, argv);

    // 2. 打开 Binlog 文件并定位到起始位置
    Binlog_file_reader reader;
    reader.open(log_file_name);
    reader.seek(start_position);

    // 3. 逐 Event 解析并过滤
    while (!reader.eof()) {
        Log_event* ev = reader.read_event();

        // 4. 检查是否在目标范围内(时间 / Position / GTID)
        if (should_skip_event(ev)) {
            delete ev;
            continue;
        }

        // 5. 输出到 stdout(可管道给 mysql 执行)
        ev->print(stdout);
        delete ev;
    }

    return 0;
}

// 事件过滤逻辑(源码:sql/log_event.cc)
bool should_skip_event(const Log_event* ev) {
    // 1. 检查时间范围
    if (ev->when.tv_sec < start_datetime || ev->when.tv_sec > stop_datetime) {
        return true;
    }

    // 2. 检查 Position 范围
    if (ev->log_pos < start_position || ev->log_pos > stop_position) {
        return true;
    }

    // 3. 检查 GTID 排除列表(MySQL 5.6+)
    if (ev->get_type_code() == GTID_LOG_EVENT) {
        Gtid_log_event* gtid_ev = (Gtid_log_event*)ev;
        if (excluded_gtids.contains(gtid_ev->get_sidno(), gtid_ev->get_gno())) {
            return true;
        }
    }

    return false;
}

10.2 实战恢复命令与参数详解

# 1. 查看 Binlog 文件列表(源码:sql/rpl_master.cc SHOW BINARY LOGS)
SHOW BINARY LOGS;
# +------------------+-----------+-----------+
# | Log_name         | File_size | Encrypted |
# +------------------+-----------+-----------+
# | mysql-bin.000001 |       177 | No        |
# | mysql-bin.000002 |       106 | No        |
# +------------------+-----------+-----------+

# 2. 查看 Binlog 内容(源码:sql/mysqlbinlog.cc)
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001
# 输出示例:
# # at 123
# #240101 10:00:00 server id 1  end_log_pos 234 CRC32 0xabcd1234
# GTID 0-1-123
# BEGIN
# /*!*/;
# # at 234
# # INSERT INTO t VALUES (1, 'test')
# # at 345
# # COMMIT

# 3. 按时间范围恢复(源码:sql/log_event.cc 时间戳过滤)
mysqlbinlog --start-datetime="2024-01-01 10:00:00" \
            --stop-datetime="2024-01-01 11:00:00" \
            mysql-bin.000001 | mysql -u root -p

# 4. 按 Position 恢复(源码:sql/binlog_reader.cc 位置定位)
mysqlbinlog --start-position=100 --stop-position=500 \
            mysql-bin.000001 | mysql -u root -p

# 5. 按 GTID 恢复(MySQL 5.6+,源码:sql/rpl_gtid.cc GTID 集合过滤)
mysqlbinlog --include-gtids="3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100" \
            --exclude-gtids="3E11FA47-71CA-11E1-9E33-C80AA9429562:50-60" \
            mysql-bin.000001 | mysql -u root -p

# 6. 跳过 DROP / TRUNCATE 等危险操作(手动编辑过滤)
mysqlbinlog mysql-bin.000001 | grep -v "DROP TABLE" | mysql -u root -p

10.3 关键配置与最佳实践

Binlog 保留策略(源码:sql/rpl_master.cc purge_logs()):

-- 自动清理过期 Binlog(避免磁盘爆满)
expire_logs_days = 7  -- 保留 7 天
max_binlog_size = 100M  -- 单个文件最大 100MB
binlog_expire_logs_seconds = 604800  -- 7 天(8.0 新参数)

-- 重要:生产环境至少保留 7-14 天,便于 PITR
-- 监控 Binlog 磁盘使用率,避免因磁盘满导致复制中断

备份恢复最佳实践

# 1. 定期全量备份 + Binlog 持续归档
mysqldump --single-transaction --master-data=2 db_name > backup_$(date +%Y%m%d).sql

# 2. 备份时记录 Binlog 位置(--master-data=2 自动记录)
# -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=154;

# 3. 恢复时先应用全量备份,再重放 Binlog
mysql -u root -p db_name < backup_20240101.sql
mysqlbinlog --start-position=154 --stop-datetime="2024-01-01 12:00:00" \
            mysql-bin.000002 | mysql -u root -p

# 4. 验证恢复结果
SELECT COUNT(*) FROM recovered_table;  -- 检查数据量
SHOW SLAVE STATUS\G  -- 检查复制状态(如果涉及主从)

📖 术语家族:Binlog 数据恢复(PITR)

字面义:Point-In-Time Recovery = 时间点恢复。 在 MySQL 中的含义:利用全量备份 + Binlog 增量重放将数据库恢复到任意历史时间点的技术,是数据库最后一道数据安全防线同家族成员(按恢复流程排列):

成员 作用 源码入口 关键参数
mysqlbinlog Binlog 解析与重放工具 sql/mysqlbinlog.cc mysqlbinlog_main() --start-position / --stop-datetime / --exclude-gtids
SHOW BINARY LOGS 查看 Binlog 文件列表 sql/rpl_master.cc show_binary_logs() Log_name / File_size / Encrypted
PURGE BINARY LOGS 清理过期 Binlog sql/rpl_master.cc purge_logs() BEFORE / TO / expire_logs_days
mysqldump --master-data 备份时记录 Binlog 位置 client/mysqldump.cc dump_slave_info() MASTER_LOG_FILE / MASTER_LOG_POS
CHANGE MASTER TO 从库指定同步起点 sql/rpl_slave.cc change_master() MASTER_LOG_FILE / MASTER_LOG_POS / MASTER_AUTO_POSITION=1(GTID)
RESET MASTER 清空所有 Binlog(危险!) sql/rpl_master.cc reset_master() 重置 gtid_purged,慎用

命名规律:恢复相关操作带 recovery / restore 语义,Binlog 管理命令用 BINARY LOGS(复数),位置定位用 LOG_FILE + LOG_POS 组合。


11. 常见问题

📖 排查题 / 调优题 / 业务选型题(如「主从延迟怎么办」/「半同步要不要开」)已在 实战问题与避坑指南 给出工程视角答案,本文不再重复,专注「机制问答」。

Q:Binlog 和 Redo Log 的区别?

Redo Log 是 InnoDB 引擎层的物理日志,记录数据页的物理修改,用于崩溃恢复,循环写;Binlog 是 Server 层的逻辑日志,记录数据变更,用于主从复制和数据恢复,追加写不会覆盖。

Q:为什么事务提交的最终标志是 Xid Event 而不是 Query(COMMIT)

Row 格式下 COMMIT 通过 Xid Event 承载,Xid 同时是 InnoDB 2PC 的链路 ID——崩溃恢复时 InnoDB 拿 Xid 到 Binlog 里找对应事务是否已落盘,以此决定 prepare 态事务提交或回滚。Query(COMMIT) 仅在 Statement 格式时用来标记文本形式的提交。

Q:从库回放 Binlog 时的并行模型是怎么进化的?

MySQL 5.6 首次引入并行复制,但粒度是「不同库」(单库仍串行);5.7 改为 LOGICAL_CLOCK,基于主库事务的 last_committedsequence_number——同一 group commit 内的事务互不依赖、可并行回放;8.0 新增 WRITESET 模式(binlog_transaction_dependency_tracking=WRITESET),基于写集冲突检测,即使没赶上同一 group commit 的事务也可并行。 Q:为什么要用 GTID?

GTID 让主从切换变得简单可靠。传统复制切换时需要手动找到新主库的 Binlog 文件名和 Position,容易出错;GTID 模式下从库自动根据全局唯一 ID 找到同步位置,大幅降低运维复杂度。

Q:半同步复制能保证数据不丢失吗?

不能完全保证。半同步只保证至少一个从库收到了 Binlog,但如果主库在收到 ACK 之前崩溃,这个事务可能已经在从库执行但主库没有提交,切换后会出现数据不一致。MGR(组复制)才能提供更强的一致性保证。