--- tags: [MySQL, 主从复制, Replication, Semi-Sync, GTID] create time: 2026-05-16 10:30 --- # 主从复制 ## 概述 MySQL 主从复制是通过 Binlog 将主库的数据变更传播到从库的过程。它是高可用架构的基础组件,也是读写分离的前提。 ## 复制架构 ```mermaid flowchart LR subgraph "Master" M1["业务写入 → DML"] --> M2["Server 层生成 Binlog"] M2 --> M3["Binlog Dump Thread"] end subgraph "Network" M3 -.Binlog Stream.-> I1["IO Thread"] end subgraph "Slave" I1 --> S1["Relay Log
中继日志"] S1 --> S2["SQL Thread"] S2 --> S3["数据文件更新"] end style M2 fill:#C44569,color:#fff style S2 fill:#00B6BC,color:#fff ``` ### 三线程模型 | 线程 | 归属 | 职责 | |------|------|------| | **Binlog Dump Thread** | Master | 连接到一个 Slave 就创建一个,负责发送 binlog | | **IO Thread** | Slave | 连接 Master,拉取 binlog 写入本地 Relay Log | | **SQL Thread** | Slave | 读取 Relay Log 重放应用到本地数据库 | ## 同步模式 ```mermaid flowchart TD subgraph "异步复制 Async" A1["Client 写入 Master"] --> A2["Master 刷盘返回 OK"] A2 -->|"后续"| A3["异步发给 Slave"] end subgraph "半同步复制 Semi-Sync" S1["Client 写入 Master"] --> S2["Master 刷盘"] S2 --> S3["等待 ≥1 个 Slave ACK"] S3 -->|"超时 10s"| S4["降级为异步 ⚠️"] S3 -->|"收到 ACK"| S5["返回 OK"] S5 --> S6["异步发给其他 Slave"] end subgraph "组复制 Group Replication" G1["Client 写入"] --> G2["Paxos 共识"] G2 --> G3["多数派确认"] G3 --> G4["并行回放"] end style A3 fill:#FF9F43,color:#000 style S5 fill:#00D866,color:#fff style G3 fill:#00B6BC,color:#fff ``` ### 异步复制(默认) 最简单的模式——Master 写完就不管了,异步发给 Slave。 ```sql -- MySQL 5.5 (已弃用) -- CHANGE MASTER TO -- MASTER_HOST='10.0.1.10', ... -- ✅ MySQL 8.0+ 标准语法 CHANGE REPLICATION SOURCE TO SOURCE_HOST='10.0.1.10', SOURCE_USER='repl_user', SOURCE_PASSWORD='password', SOURCE_PORT=3306, SOURCE_AUTO_POSITION = 1; -- GTID 模式自动定位 START REPLICA; ``` > [!WARNING] 异步复制的风险 > Master 宕机后,尚未传输到 Slave 的数据会丢失。对于金融系统这是不可接受的。 ### 半同步复制(Semi-Sync) 至少一个从库确认收到 Binlog 后,Master 才返回写成功。 ```ini # Master 端配置 plugin_load_add = semisync_master rpl_semi_sync_master_enabled = 1 rpl_semi_sync_master_timeout = 10000 # 10 秒超时后降级为异步 rpl_semi_sync_master_wait_point = AFTER_SYNC # 刷盘后再等待 ``` ```ini # Slave 端配置 plugin_load_add = semisync_slave rpl_semi_sync_slave_enabled = 1 ``` > [!NOTE] 半同步不是银弹 > - 从库收到 Binlog ≠ 已从磁盘持久化 > - 如果 Master 和从库同时断电,仍可能丢数据 > - 网络分区时 Master 会超时降级为异步 > > **最佳实践**:Semi-Sync + 定期备份 + PITR 恢复组合使用 ## GTID 复制 GTID(Global Transaction Identifier)给每个事务分配全局唯一的 ID,替代传统的 position-based 复制。 ``` GTID 格式: source_id:transaction_id 例: 3E11FA47-71CA-11E1-9E33-C80AA9429562:23 ↑ UUID of server ↑ Sequence number ``` ### GTID 复制流程 ```mermaid flowchart TD A["Client 事务写入 Master"] --> B["Server 层分配 GTID
source_id:next_seq"] B --> C["写入 Binlog 事件
带 GTID 标记"] C --> D["Binlog Dump 线程发送
GTID + 事件到 Slave"] D --> E["Slave IO Thread 写入
Relay Log (含 GTID)"] E --> F{"GTID Set
是否已包含此 GTID?"} F -->|是| G["跳过重复事件 ✅"] F -->|否| H["SQL Worker 重放事务"] H --> I["更新 Slave gtid_executed"] G --> J["继续下一个事件"] I --> J style B fill:#C44569,color:#fff style F fill:#FFD166,color:#000 style H fill:#00B6BC,color:#fff ``` > [!NOTE] GTID 的一致性约束 > 启用 `enforce_gtid_consistency = ON` 后,以下操作被**禁止**: > - 对非事务引擎(如 MyISAM)的 DML > - 创建/删除临时表 > - `CREATE TABLE ... SELECT`(混合语句级和语句级二进制日志) > - 对视图的 DML(无法追踪来源表) ### GTID vs 传统 Position | 特性 | Position-based | GTID-based | |------|---------------|------------| | **定位方式** | 文件名 + position 号 | GTID set | | **跨库切换** | 需要手动查 position | 自动定位 | | **跳过事务** | 困难 | `SET gtid_next = '...'; BEGIN; COMMIT; SET gtid_next = AUTOMATIC;` | | **一致性检查** | 手动比对 | `pt-table-checksum` + GTID 验证 | ```ini # 启用 GTID gtid_mode = ON enforce_gtid_consistency = ON master_info_repository = TABLE # 复制信息存在表中而非文件 relay_log_info_repository = TABLE ``` ## 并行复制(Multithreaded Slave, MTS) MySQL 5.6+ 引入从库并行回放,大幅提升复制延迟处理能力。 ```ini # MySQL 5.x(已弃用) # slave_parallel_type = LOGICAL_CLOCK # slave_parallel_workers = 8 # ✅ MySQL 8.0+ 标准命名 replica_parallel_type = LOGICAL_CLOCK # 基于 GTID 的并发回放 replica_parallel_workers = 8 # 并发 worker 数量 ``` ### MTS 并行模式对比 ```mermaid flowchart LR subgraph "DATABASE(默认)" D1["Relay Log 事件"] --> D2{"按 database\n分桶"} D2 -->|"db_a"| W1["Worker 1"] D2 -->|"db_b"| W2["Worker 2"] D2 -->|"db_c"| W3["Worker 3"] W1 & W2 & W3 --> J1["写入数据"] end subgraph "GROUP_TRANSACTIONS" G1["Relay Log 事件"] --> G2{"判断事务\n依赖关系"} G2 -->|"无冲突"| A1["组 1 → 并行执行"] G2 -->|"有冲突"| A2["组 2 → 串行等待"] G1 -->|"无冲突"| A1 A1 --> J2["写入数据"] A2 --> J2 end style A1 fill:#00D866,color:#fff style A2 fill:#FF9F43,color:#000 ``` | 维度 | `DATABASE` | `GROUP_TRANSACTIONS` | |------|-----------|---------------------| | **分组依据** | database 名称 | 事务间的写集依赖关系 | | **并发度** | 受限于 database 数量 | 取决于并发事务比例,通常更高 | | **精确度** | 粗粒度——同一 DB 内串行 | 细粒度——无依赖即可并行 | | **适用场景** | schema 设计良好的系统 | 多库共享、跨库事务较多的场景 | | **引入版本** | MySQL 5.6 | MySQL 5.7+ | > [!TIP] 如何选择 > - 如果业务按 database 做数据隔离(每个 tenant 一个 DB),选 `DATABASE` > - 如果多个业务共享少数几个 database,选 `GROUP_TRANSACTIONS` ### LOGICAL_CLOCK 原理 > [!INFO] `LOGICAL_CLOCK` = GTID 驱动的并行回放 > 无论选择 `DATABASE` 还是 `GROUP_TRANSACTIONS`,底层引擎都是 LOGICAL_CLOCK。 > 区别仅在于如何把连续事件流切分成可并行的逻辑组。 ```mermaid flowchart TD A["Relay Log 事件流"] --> B{"按 database 分组"} B --> C["DB1 的事件队列"] B --> D["DB2 的事件队列"] B --> E["DBn 的事件队列"] C --> W1["Worker 1 执行 DB1 事件"] D --> W2["Worker 2 执行 DB2 事件"] E --> Wn["Worker n 执行 DBn 事件"] style W1 fill:#00D866,color:#fff style W2 fill:#00B6BC,color:#fff style Wn fill:#FF9F43,color:#000 ``` > [!TIP] Parallel Replication 的限制 > - 基于 database 的并行要求同一 database 内没有冲突事务 > - 跨 database 的操作无法并行 > - innodb_table_locks=OFF 且 autocommit=1 时才能并行(避免表锁冲突) ## 监控复制状态 ```sql -- MySQL 8.0+ 已弃用 SHOW SLAVE STATUS -- SHOW SLAVE STATUS\G -- ✅ 使用 performance_schema 视图 SELECT * FROM performance_schema.replication_connection_status; SELECT * FROM performance_schema.replication_applier_status; -- ⚠️ 以上两表各返回一行,多从库场景下需要关联: -- connection: 一条代表 Master → Slave 的 IO 连接 -- applier: 每行代表一个 SQL Worker 线程 ``` ```sql -- 实时延迟监控(MySQL 8.0+, GTID 模式) SELECT MAX(latency) / 1000000000 AS max_lag_seconds FROM ( SELECT MAX(clock_timestamp) - MIN(clock_start) AS latency FROM performance_schema.replication_applier_status_by_worker ) lag; ``` ## 常见问题与排查 ### IO 线程断开(Slave_IO_Running: No) ```mermaid flowchart TD A["IO Thread 断连"] --> B{"原因定位"} B -->|网络不通| C["检查防火墙 / 安全组端口"] B -->|Binlog 已过期| D["purge binary logs / 调整 expire_logs_days"] B -->|认证失败| E["核对 user 权限 & password"] B -->|Server UUID 冲突| F["重置 slave server_uuid"] C --> G["CHANGE REPLICATION SOURCE TO
SOURCE_LOG_FILE = '...',
SOURCE_LOG_POS = ..."] D --> G E --> G F --> G G --> H["START REPLICA"] H --> I["确认 IO 恢复"] style A fill:#FF6B6B,color:#fff style I fill:#00D866,color:#fff ``` **常见排查步骤:** ```sql -- 1. 查看具体错误信息 SELECT LAST_ERROR_MESSAGE FROM performance_schema.replication_connection_status; -- 2. 检查 Master Binlog 是否已被清理 SHOW MASTER LOGS; -- 3. 如果 Binlog 已过期,需要全量恢复(mysqldump + --single-transaction) -- 或使用 xtrabackup 做 In-place Slave 重建 ``` ### SQL 线程卡住(Slave_SQL_Running: No) 这通常是由于数据不一致导致的——例如从库被手动修改过。 > [!WARNING] 不要直接跳过! > 盲目 `sql_slave_skip_counter` 可能导致数据静默丢失。必须先确认事务内容。 ```sql -- ⚠️ MySQL 5.7 及以下(已弃用) -- SET GLOBAL sql_slave_skip_counter = 1; -- STOP SLAVE; START SLAVE; -- ✅ MySQL 8.0+ GTID 模式:跳过指定事务 SET GTID_NEXT='3E11FA47-71CA-11E1-9E33-C80AA9429562:100'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; -- 重新加入复制组 STOP REPLICA; SET GLOBAL gtid_purged = '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100'; START REPLICA; ``` ### 延迟过大如何诊断 ```mermaid flowchart LR A["Seconds_Behind_Master 大"] --> B{"IO 延迟还是 SQL 延迟?"} B -->|"Relay_Log_Space 持续增长"| C["SQL Worker 回放太慢
→ 增加 replica_parallel_workers
→ 考虑 GROUP_TRANSACTIONS"] B -->|"Read_Master_Log_Pos 跟丢"| D["网络带宽不足 / Master
写入量太大
→ 检查网络 / 限制 binlog 传输大小"] B -->|"DDL 执行中"| E["大表 ALTER TABLE 阻塞
→ 使用 pt-online-schema-change"] style C fill:#00B6BC,color:#fff style D fill:#FF9F43,color:#000 style E fill:#C44569,color:#fff ``` --- ## 关联笔记 - [[hhs/MySQL/29-Binary Log]] — Binlog 是主从复制的数据源 - [[hhs/MySQL/31-MHA / Orchestrator]] — 自动故障切换工具 - [[hhs/MySQL/33-Group Replication]] — Paxos 多主复制进阶方案 - [[hhs/MySQL/32-读写分离与代理]] — 基于主从架构的流量分发