实现主备一致性的方式:使用 Binlog 日志。
备库处于 readonly 的优点: 1.避免误操作; 2.避免双 M 导致不一致; 3.基于 readonly 区分主备库。主备同步的流程: 1.备库配置主库的连接方式、binlog 文件名、日志偏移量
2.start slave 命令启动两个线程,一个 io 线程进行连接,拉取 binlog 放到 relaylog 中,一个 sql 线程执行 relaylog 重放 binlog主备同步的时候,会设计到 Binlog 存储格式 的问题。
其中需要注意,mixed 格式下,now() 采用 statement 格式,但是前面会有个 SET TIMESTAMP 的语句。双 M 结构可能存在循环复制的原因是:
A 库将 binlog 发给 B 库,B 库重放 binlog 后,将 sql 又发给 A 库。当双 M 结构时,如何避免循环复制?
1.binlog 每一条语句有一个 server id; 2.备库重放 binlog 的时候,生成 server id 相同的 binlog。 3.只重放非自己 server id 的语句;当然也存在不正当使用数据库导致的循环复制的情况。
Releted:24.MySQL保证主备一致性、24.MySQL是怎么保证主备一致的?.pdf。
主备同步的方式
MySQL 基于 Binlog 日志 来进行主备库的数据同步。
建议将备库设置为 readonly 模式:
- 避免运营类的查询语句在使用备库时,误操作。
- 防止切换逻辑有 bug,导致出现双写,进而主备不一致。
- 基于 readonly 状态区分主备库。
readonly 权限对于 super 权限用户是无效的,而同步更新的线程就是 super 权限。

主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写 binlog。
备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:
- 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
- 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
- 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。
- 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
- sql_thread 读取中转日志,解析出日志里的命令,并执行。
后来由于多线程复制方案的引入,sql_thread 演化成为了多个线程。
binlog 存储格式问题
statement、mixed、row 格式。
24.MySQL是怎么保证主备一致的?.pdf 中有查看 binlog 日志的内容。
循环复制问题
实际生产上使用比较多的是双 M 结构,如下是主备切换流畅:

双 M 结构和 M-S 结构,其实区别只是多了一条线,即:节点 A 和 B 之间总是互为主备关系。这样在切换的时候就不用再修改主备关系。
双 M 结构还有一个问题需要解决。业务逻辑在节点 A 上更新了一条语句,然后再把生成的 binlog 发给节点 B,节点 B 执行完这条更新语句后也会生成 binlog。(我建议你把参数 log_slave_updates 设置为 on,表示备库执行 relay log 后生成 binlog)。
MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的 server id。因此,我们可以用下面的逻辑,来解决两个节点间的循环复制的问题:
-
规定两个库的 server id 必须不同,如果相同,则它们之间不能设定为主备关系;
-
一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 相同的新的 binlog;
-
每个库在收到从自己的主库发过来的日志后,先判断 server id,如果跟自己的相同,表示这个日志是自己生成的,就直接丢弃这个日志。
不正当使用导致的循环复制
在上面双 M 下,依然可能存在循环复制的问题,是一些误操作导致的情况。
-
当主库更新事务后,对自己的 server_id 进行了更新,导致日志重传回来后,server_id 不一致导致再执行。
set global server_id=x; -
三个节点,依赖关系为 A → B,B ←> C。那么当事务在 A 节点上执行时,B 和 C 之间存在循环复制。因为 server_id 是 A 的 id。
# 解决办法 stop slave; CHANGE MASTER TO IGNORE_SERVER_IDS = (server_id_of_B); start slave; # 过一段时间后,再更改回来 stop slave; CHANGE MASTER TO IGNORE_SERVER_IDS = (); start slave;