MySQL 是怎么保证主备一致的在数据库的世界里主备同步Master-Slave Replication就像武侠小说里的“分身术”——主库负责写备库负责读一旦主库挂了备库能立刻顶上。但这里有个灵魂拷问备库凭什么能和主库保持一致如果主库刚写完一条数据备库还没来得及同步主库就宕机了数据不就丢了吗今天咱们就用大白话拆解 MySQL 的主备同步机制看看它是怎么做到“滴水不漏”的。—## 一、主备同步的“老剧本”binlog 和 relay logMySQL 的主备同步核心就靠两个“记事本”-主库的 binlog记录所有数据变更操作比如 INSERT、UPDATE、DELETE。-备库的 relay log从主库拉取 binlog 后先存到本地再“回放”到备库。整个流程像一场接力赛bash主库执行 SQL → 写入 binlog → 备库 IO 线程拉取 → 写入 relay log → 备库 SQL 线程回放听起来简单但问题来了备库怎么知道该从 binlog 的哪个位置开始拉取如果主库在写入 binlog 后、还没发给备库就宕机了怎么办—## 二、关键机制binlog 的“三阶段写入”和“同步复制”### 1. 主库的 binlog 何时写入MySQL 默认是异步复制意思是主库写完 binlog 后并不等备库确认就直接返回给客户端。这样性能高但风险是主库宕机时备库可能少收到几条 binlog。为了解决这个问题MySQL 提供了半同步复制Semisync Replication它要求主库在提交事务前至少等待一个备库确认收到 binlog。如果备库没确认主库会等待直到超时后再降级为异步。代码示例配置半同步复制在 MySQL 中执行sql-- 主库安装半同步插件INSTALL PLUGIN rpl_semi_sync_master SONAME semisync_master.so;SET GLOBAL rpl_semi_sync_master_enabled 1;SET GLOBAL rpl_semi_sync_master_timeout 1000; -- 超时1秒-- 备库安装半同步插件INSTALL PLUGIN rpl_semi_sync_slave SONAME semisync_slave.so;SET GLOBAL rpl_semi_sync_slave_enabled 1;-- 重启备库 IO 线程STOP SLAVE IO_THREAD;START SLAVE IO_THREAD;半同步复制的意义主库提交事务时必须等到备库把 binlog 写入 relay log 并返回 ACK才向客户端返回成功。这样主库宕机时至少有一个备库拥有最新数据。—### 2. 备库如何“回放” binlog备库的 SQL 线程会读取 relay log然后执行其中的 SQL。但注意binlog 里存的不是原始 SQL而是“事件”比如“插入一行记录”而不是“INSERT INTO … WHERE …”。这是因为 binlog 要支持主备切换后的数据一致性必须记录“行级别”的变更。看一个实际例子假设主库执行sql-- 主库执行把 id1 的 age 加 1UPDATE user SET age age 1 WHERE id 1;binlog 里记录的是类似### UPDATE user### WHERE### 11 /* id */### SET### 325 /* age 从 24 改为 25 */备库回放时直接按照这个“行变更”去更新不需要重新执行 WHERE 条件所以即使表结构有细微差异也能保证最终一致。—## 三、主备切换的“惊险一跃”如何保证不丢数据如果主库突然宕机需要把备库提升为主库。这时最怕的是备库的 relay log 还没执行完或者备库的 binlog 位置落后于主库。MySQL 8.0 引入了增强半同步复制Lossless Semi-sync它把“备库写入 relay log”和“主库提交事务”绑定在一起主库在提交事务时必须等备库把 binlog 写入 relay log 并落盘才算成功。看一个配置示例MySQL 8.0 中sql-- 主库SET PERSIST rpl_semi_sync_master_enabled ON;SET PERSIST rpl_semi_sync_master_wait_point AFTER_SYNC; -- 默认值等备库写入后主库才提交-- 备库SET PERSIST rpl_semi_sync_slave_enabled ON;AFTER_SYNC 的含义是主库把 binlog 发给备库后**等待备库写盘然后主库才提交事务**。这样即使主库宕机备库也一定拥有这条 binlog因此不会丢数据。---## 四、备库延迟怎么办并行复制来救场主备同步最大的敌人是**延迟**比如备库执行慢导致主库已经写入 100 万条备库才执行到 50 万条。在 MySQL 5.7 之前备库是**单线程回放**延迟容易积累。MySQL 5.7 提供了**并行复制Multi-Threaded Replication**可以把 relay log 按数据库或表分组多线程并行执行。配置示例sql-- 备库设置并行复制线程数假设 4 个线程STOP SLAVE;SET GLOBAL slave_parallel_workers 4;SET GLOBAL slave_parallel_type LOGICAL_CLOCK; – 基于逻辑时钟自动判断事务之间的依赖START SLAVE;这样备库可以同时执行多个不冲突的事务大大降低延迟。---## 五、实战测试模拟主库宕机看备库是否丢数据下面用一个 Python 脚本模拟主库写入事务同时监控备库数据pythonimport pymysqlimport time# 连接主库和备库master_conn pymysql.connect(host‘127.0.0.1’, port3306, user‘root’, password‘123456’)slave_conn pymysql.connect(host‘127.0.0.1’, port3307, user‘root’, password‘123456’)# 主库写入一条数据with master_conn.cursor() as cursor: cursor.execute(“CREATE DATABASE IF NOT EXISTS test_db”) cursor.execute(“USE test_db”) cursor.execute(“CREATE TABLE IF NOT EXISTS t (id INT PRIMARY KEY, val VARCHAR(100))”) cursor.execute(“INSERT INTO t (id, val) VALUES (1, ‘hello’)”)master_conn.commit()# 立即杀掉主库进程模拟宕机import osos.system(“kill -9 master_mysql_pid”)# 检查备库是否有这条数据等待几秒让同步完成time.sleep(2)with slave_conn.cursor() as cursor: cursor.execute(“USE test_db”) cursor.execute(“SELECT * FROM t WHERE id1”) result cursor.fetchone()if result: print(“备库数据完整值 ”, result[1])else: print(“备库数据缺失”)如果配置了半同步复制这里的输出应该是“备库数据完整”因为主库在提交前已经等备库确认了。—## 六、总结MySQL 主备一致的核心是一套“日志接力”机制1.binlog 记录所有变更备库通过 IO 线程拉取写入 relay log。2.半同步复制让主库提交事务前必须等备库确认避免主库宕机丢数据。3.并行复制解决了备库延迟问题。4. 主备切换时通过 binlog 位置和 relay log 的完整性保证不丢数据。但要注意没有绝对的一致异步复制下主备可能有秒级延迟半同步下如果超时降级也可能丢数据。所以设计高可用系统时还要结合心跳检测、自动切换等机制才能做到“尽量不丢快速恢复”。希望这篇文章能帮你更好地理解 MySQL 主备同步的底层逻辑。如果有疑问欢迎在评论区讨论