Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL是怎么保证主备一致的

MySQL是怎么保证主备一致的

作者:香农派我最爱

大家知道 binlog 可以用来归档,也可以用来做主备同步,但它的内容是什么样的呢?为什么备库执行了 binlog 就可以跟主库保持一致了呢,本文就详细的介绍一下

抛出问题:大家知道 binlog 可以用来归档,也可以用来做主备同步,但它的内容是什么样的呢?为什么备库执行了 binlog 就可以跟主库保持一致了呢?

MySQL 主备的基本原理

在这里插入图片描述

图 1 MySQL 主备切换流程

在状态 1 中,客户端的读写都直接访问节点 A,而节点 B 是 A 的备库,只是将 A 的更新都同步过来,到本地执行。这样可以保持节点 B 和 A 的数据是相同的。
当需要切换的时候,就切成状态 2。这时候客户端读写访问的都是节点 B,而节点 A 是 B 的备库。
在状态 1 中,虽然节点 B 没有被直接访问,但是建议你把节点 B(也就是备库)设置成只读(readonly)模式。这样做,有以下几个考虑:

我把备库设置成只读了,还怎么跟主库保持同步更新呢?
这个问题,你不用担心。因为 readonly 设置对超级 (super) 权限用户是无效的,而用于同步更新的线程,就拥有超级权限。

在这里插入图片描述

图 2 主备流程图

主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写 binlog。备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:

后来由于多线程复制方案的引入,sql_thread 演化成为了多个线程

binlog 的三种格式对比

binlog 有两种格式,一种是 statement,一种是 row。可能你在其他资料上还会看到有第三种格式,叫作 mixed,其实它就是前两种格式的混合。

mysql> CREATE TABLE t (
	id int(11) NOT NULL,
	a int(11) DEFAULT NULL,
	t_modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (id),
	KEY a (a),
	KEY t_modified(t_modified)
) ENGINE=InnoDB;

insert into t values(1,1,'2018-11-13');
insert into t values(2,2,'2018-11-12');
insert into t values(3,3,'2018-11-11');
insert into t values(4,4,'2018-11-10');
insert into t values(5,5,'2018-11-09');

如果要在表中删除一行数据的话,我们来看看这个 delete 语句的 binlog 是怎么记录的。

mysql> delete from t /comment/  where a>=4 and t_modified<='2018-11-10' limit 1;

当 binlog_format=statement 时,binlog 里面记录的就是 SQL 语句的原文。你可以用mysql> show binlog events in ‘master.000001';命令看 binlog 中的内容。

在这里插入图片描述

图 3 statement 格式 binlog 示例

现在,我们来看一下图 3 的输出结果。

为了说明 statement 和 row 格式的区别,我们来看一下这条 delete 命令的执行效果图:

在这里插入图片描述

图 4 delete 执行 warnings

运行这条 delete 命令产生了一个 warning,原因是当前 binlog 设置的是 statement 格式,并且语句中有 limit,所以这个命令可能是 unsafe 的。为什么这么说呢?这是因为 delete 带 limit,很可能会出现主备数据不一致的情况

如果 delete 语句使用的是索引 a,那么会根据索引 a 找到第一个满足条件的行,也就是说删除的是 a=4 这一行;
但如果使用的是索引 t_modified,那么删除的就是 t_modified='2018-11-09'也就是 a=5 这一行。

由于 statement 格式下,记录到 binlog 里的是语句原文,因此可能会出现这样一种情况:在主库执行这条 SQL 语句的时候,用的是索引 a;而在备库执行这条 SQL 语句的时候,却使用了索引 t_modified。因此,MySQL 认为这样写是有风险的

如果我把 binlog 的格式改为 binlog_format=‘row', 是不是就没有这个问题了呢?

在这里插入图片描述

图 5 row 格式 binlog 示例

与 statement 格式的 binlog 相比,前后的 BEGIN 和 COMMIT 是一样的。但是,row 格式的 binlog 里没有了 SQL 语句的原文,而是替换成了两个 event:Table_map 和 Delete_rows。

借助 mysqlbinlog 工具,用下面这个命令解析和查看 binlog 中的内容。这个事务的 binlog 是从 8900 这个位置开始的,所以可以用 start-position 参数来指定从这个位置的日志开始解析。

mysqlbinlog  -vv data/master.000001 --start-position=8900;

在这里插入图片描述

图 6 row 格式 binlog 示例的详细信息

从这个图中,我们可以看到以下几个信息:

最后的 Xid event,用于表示事务被正确地提交了。

binlog_format 使用 row 格式的时候,binlog 里面记录了真实删除行的主键 id,这样 binlog 传到备库去的时候,就肯定会删除 id=4 的行,不会有主备删除不同行的问题

为什么会有 mixed 格式的 binlog?

因为有些 statement 格式的 binlog 可能会导致主备不一致,所以要使用 row 格式

row 格式的缺点是,很占空间。比如你用一个 delete 语句删掉 10 万行数据,用 statement 的话就是一个 SQL 语句被记录到 binlog 中,占用几十个字节的空间。但如果用 row 格式的 binlog,就要把这 10 万条记录都写到 binlog 中。这样做,不仅会占用更大的空间,同时写 binlog 也要耗费 IO 资源,影响执行速度

所以,MySQL 就取了个折中方案,也就是有了 mixed 格式的 binlog。mixed 格式的意思是,MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式。 也就是说,mixed 格式可以利用 statment 格式的优点,同时又避免了数据不一致的风险。

现在越来越多的场景要求把 MySQL 的 binlog 格式设置成 row。这么做的理由有很多,举一个可以直接看出来的好处:恢复数据

我们就分别从 delete、insert 和 update 这三种 SQL 语句的角度,来看看数据恢复的问题。

由 delete、insert 或者 update 语句导致的数据操作错误,需要恢复到操作之前状态的情况,也时有发生。MariaDB 的Flashback工具就是基于上面介绍的原理来回滚数据的。

mysql> insert into t values(10,10, now());

把 binlog 格式设置为 mixed,你觉得 MySQL 会把它记录为 row 格式还是 statement 格式呢?

在这里插入图片描述

图 7 mixed 格式和 now()

MySQL 用的居然是 statement 格式。接下来,我们再用 mysqlbinlog 工具来看看:

在这里插入图片描述

图 8 TIMESTAMP 命令

原来 binlog 在记录 event 的时候,多记了一条命令:SET TIMESTAMP=1546103491。它用 SET TIMESTAMP 命令约定了接下来的 now() 函数的返回时间。通过这条 SET TIMESTAMP 命令,MySQL 就确保了主备数据的一致性。

重放 binlog 数据的时候,是这么做的:用 mysqlbinlog 解析出日志,然后把里面的 statement 语句直接拷贝出来执行
你现在知道了,这个方法是有风险的。因为有些语句的执行结果是依赖于上下文命令的,直接执行的结果很可能是错误的

所以,用 binlog 来恢复数据的标准做法是,用 mysqlbinlog 工具解析出来,然后把解析结果整个发给 MySQL 执行。类似下面的命令:

mysqlbinlog master.000001  --start-position=2738 --stop-position=2942 | mysql -h127.0.0.1 -P13000 -u$user -p$pwd;

这个命令的意思是,将 master.000001 文件里面从第 2738 字节到第 2942 字节中间这段内容解析出来,放到 MySQL 去执行。

循环复制问题

binlog 的特性确保了在备库执行相同的 binlog,可以得到与主库相同的状态。我们可以认为正常情况下主备的数据是一致的。也就是说,图 1 中 A、B 两个节点的内容是一致的。其实,图 1 中我画的是 M-S 结构,但实际生产上使用比较多的是双 M 结构,也就是图 9 所示的主备切换流程。

在这里插入图片描述

图 9 MySQL 主备切换流程 -- 双 M 结构

节点 A 和 B 之间总是互为主备关系。这样在切换的时候就不用再修改主备关系。

但是,双 M 结构还有一个问题需要解决

业务逻辑在节点 A 上更新了一条语句,然后再把生成的 binlog 发给节点 B,节点 B 执行完这条更新语句后也会生成 binlog。(我建议你把参数 log_slave_updates 设置为 on,表示备库执行 relay log 后生成 binlog)。

那么,如果节点 A 同时是节点 B 的备库,相当于又把节点 B 新生成的 binlog 拿过来执行了一次,然后节点 A 和 B 间,会不断地循环执行这个更新语句,也就是循环复制了。这个要怎么解决呢

从上面的图 6 中可以看到,MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的 server id。因此,我们可以用下面的逻辑,来解决两个节点间的循环复制的问题:

按照这个逻辑,如果我们设置了双 M 结构,日志的执行流就会变成这样:

总结:

到此这篇关于MySQL是怎么保证主备一致的的文章就介绍到这了,更多相关MySQL是怎么保证主备一致的内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文