MySQL 主从复制数据不一致的解决方法


Posted in MySQL onMarch 18, 2022

今天来说说 MySQL 主从复制数据不一致的问题,通过几个具体的案例,来向小伙伴们展示 binlog 不同 format 之间的区别。

1. 准备工作

以下配置基于 Docker。

我这里有一张简单的图向大伙展示 MySQL 主从的工作方式:

MySQL 主从复制数据不一致的解决方法

这里,我们准备两台机器:

  • 主机:10.3.50.27:33061
  • 从机:10.3.50.27:33062

1.1 主机配置

主机的配置就三个步骤,比较容易:

1. 授权给从机服务器

GRANT REPLICATION SLAVE ON *.* to 'rep1'@'10.3.50.27' identified by '123';
FLUSH PRIVILEGES;

这里表示配置从机登录用户名为 rep1,密码为 123,并且必须从 10.3.50.27 这个地址登录,登录成功之后可以操作任意库中的任意表。其中,如果不需要限制登录地址,可以将 IP 地址更换为一个 %

注意,在 MySQL8 里边,这块有一些变化。MySQL8 中用户创建和授权需要分开,不能像上面那样一步到位,具体方式如下:

CREATE USER `rep1`@`10.3.50.27` IDENTIFIED WITH caching_sha2_password BY 'javaboy.COM';

GRANT Replication Slave ON *.* TO `rep1`@`10.3.50.27`;

2. 修改主库配置文件

开启 binlog ,并设置 server-id ,每次修改配置文件后都要重启 MySQL 服务才会生效

开启 binlog 主要是修改 MySQL 的配置文件 mysqld.cnf,该文件在容器的 /etc/mysql/mysql.conf.d 目录下。

MySQL 主从复制数据不一致的解决方法

针对该配置文件,我们做如下修改:

[mysqld]
# 这个参数表示启用 binlog 功能,并指定 binlog 的存储目录
log-bin=javaboy_logbin
# 设置 binlog_format 格式
binlog_format=STATEMENT
# 设置一个 binlog 文件的最大字节
# 设置最大 100MB
max_binlog_size=104857600

# 设置了 binlog 文件的有效期(单位:天)
expire_logs_days = 7

# binlog 日志只记录指定库的更新(配置主从复制的时候会用到)
binlog-do-db=javaboy_db

# binlog 日志不记录指定库的更新(配置主从复制的时候会用到)
#binlog-ignore-db=javaboy_no_db

# 写缓存多少次,刷一次磁盘,默认 0 表示这个操作由操作系统根据自身负载自行决定多久写一次磁盘
# 1 表示每一条事务提交都会立即写磁盘,n 则表示 n 个事务提交才会写磁盘
sync_binlog=0

# 为当前服务取一个唯一的 id(MySQL5.7 开始需要)
server-id=1

各项配置的含义松哥已经在注视中说明了。截图如下:

如下图:

MySQL 主从复制数据不一致的解决方法

  • log-bin:同步的日志路径及文件名,一定注意这个目录要是 MySQL 有权限写入的(我这里是偷懒了,直接放在了下面那个datadir下面)。
  • binlog-do-db:要同步的数据库名,当从机连上主机后,只有这里配置的数据库才会被同步,其他的不会被同步。
  • server-id: MySQL 在主从环境下的唯一标志符,给个任意数字,注意不能和从机重复。

修改 binlog_format 的值为 STATEMENT,这一点很关键。

配置完成后重启 MySQL 服务端:

docker restart mysql33061

3. 查看主服务器当前二进制日志名和偏移量

这个操作的目的是为了在从数据库启动后,从这个点开始进行数据的恢复:

show master status;

MySQL 主从复制数据不一致的解决方法

再看一眼 binlog_format 设置成功没:

MySQL 主从复制数据不一致的解决方法

可以看到,没问题。

至此,主机配置完成。

1.2 从机配置

从机的配置也比较简单,我们一步一步来看:

1. 在/etc/my.cnf 添加配置

MySQL 主从复制数据不一致的解决方法

注意从机这里只需要配置一下 server-id 即可。

注意:如果从机是从主机复制来的,即我们通过复制 CentOS 虚拟机获取了 MySQL 实例 ,此时两个 MySQL 的 uuid 一样(正常安装是不会相同的),这时需要手动修改,修改位置在 /var/lib/mysql/auto.cnf ,注意随便修改这里几个字符即可,但也不可太过于随意,例如修改了 uuid 的长度。

配置完成后,记得重启从机。

2. 使用命令来配置从机

change master to master_host='10.3.50.27',master_port=33061,master_user='rep1',master_password='123',master_log_file='javaboy_logbin.000001',master_log_pos=154;

这里配置了主机地址、端口以及从机登录主机的用户名和密码,注意最后两个参数要和 master 中的保持一致。

注意,由于 MySQL8 密码插件的问题,这个问题同样会给主从配置带来问题,所以在 MySQL8 配置主从上,上面这行命令需要添加 get_master_public_key=1,完整命令如下:

change master to master_host='10.3.50.27',master_port=33061,master_user='rep1',master_password='123',master_log_file='javaboy_logbin.000001',master_log_pos=154,get_master_public_key=1;

3. 启动 slave 进程

start slave;

启动之后查看从机状态:

show slave status\G;

MySQL 主从复制数据不一致的解决方法

4. 查看 slave 的状态

主要是下面两项值都要为为 YES,则表示配置正确:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes

至此,配置完成,主机创建库,添加数据,从机会自动同步。

如果这两个有一个不为 YES ,表示主从环境搭建失败,此时可以阅读日志,查看出错的原因,再具体问题具体解决。

具体的同步过程如下:

  • 首先在从机 33062 上通过 change master 命令,设置主机 33061 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog(master_log_pos),这个位置包含文件名和日志偏移量。
  • 在从机 33061 上执行 start slave 命令,这时候从机会启动两个线程,分别是 io_thread 和 sql_thread。
  • io_thread 负责与主机建立连接。
  • 主机 33061 校验完用户名、密码后,开始按照从机 33062 传过来的位置,从本地读取 binlog,发给 33062。
  • 从机 33062 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
  • sql_thread 线程读取中转日志,解析出日志里的命令,并执行。

大致就是这样一个流程。

2. 数据不一致问题

接下来我们创建一个 javaboy_db 的数据库,并在里边创建一个 user 表,user 表的定义如下:

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `uuid` varchar(128) DEFAULT NULL,
  `name` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

接下来我们在主机中向 user 表中插入一条记录,如下:

MySQL 主从复制数据不一致的解决方法

按道理,这条记录会同步到 33062 这台从机上:

MySQL 主从复制数据不一致的解决方法

大家看到,数据确实同步了,但是 uuid 却不一样。

3. 原因分析

我们知道,MySQL 主从同步最主要的依据就是 binlog,master 将自己的 binlog 发给 slave,slave 重放之后获取和 master 一致的数据。

那我们就来看看 master 生成的 binlog 是啥样子。

我们按照事件的方式来看一下 binlog,命令格式如下:

show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];

这个表示以事件的方式来查看 binlog,这里涉及到几个参数:

  • log_name:可以指定要查看的 binlog 日志文件名,如果不指定的话,表示查看最早的 binlog 文件。
  • pos:从哪个 pos 点开始查看,凡是 binlog 记录下来的操作都有一个 pos 点,这个其实就是相当于我们可以指定从哪个操作开始查看日志,如果不指定的话,就是从该 binlog 的开头开始查看。
  • offset:这是是偏移量,不指定默认就是 0。
  • row_count:查看多少行记录,不指定就是查看所有。

查看命令如下(我这里就从 pos 为 154 的位置开始):

show binlog events IN 'javaboy_logbin.000001' FROM 154;

查看结果如下(部分):

MySQL 主从复制数据不一致的解决方法

从图中可以看到,记录在 binlog 原文中的日志是:use javaboy_db; insert into user(uuid,name) values(uuid(),'javaboy')

这句 SQL 将来同步到 slave 之后,slave 照着执行一下,那必然出现执行结果不一致的问题,因为 uuid() 函数每次执行结果都不一样。

现在小伙伴们看明白问题的原因了吧。

4. 问题解决

问题倒也好解决,上篇文章我们说过,我们可以将 binlog_format 设置为 ROW 来解决这个问题。

具体操作步骤如下。

在主机中,修改 /etc/mysql/mysql.conf.d/mysqld.cnf 配置文件,将 binlog_format 改为 ROW,如下:

MySQL 主从复制数据不一致的解决方法

修改完成后,重启主机,主机重启之后,会产生新的 binlog 文件,所以我们需要重新查看主机的最新状态并重新配置从机,先来看主机,如下:

MySQL 主从复制数据不一致的解决方法

以此为依据,让从机重新连接主机,在从机上再进行如下操作:

stop slave;

change master to master_host='10.3.50.27',master_port=33061,master_user='rep1',master_password='123',master_log_file='javaboy_logbin.000002',master_log_pos=794;

start slave;

重新配置完从机之后,我们继续向 user 表插入一条数据,插入完成后,我们再去看从机的数据,发现此时的数据已经是一致的了。

解决这个问题,我们最主要的更改就是修改了 binlog_format 为 ROW,当我们把 binlog_format 改为 ROW 之后,我们来看看此时 binlog 中都记录了啥。

show binlog events IN 'javaboy_logbin.000002' FROM 794;

MySQL 主从复制数据不一致的解决方法

大家看到,在 BEGIN 和 COMMIT 之间,就是我们的数据修改操作。

  • Table_map:这一行是说明了接下来要操作 javaboy_db.user 表。
  • Write_rows:这一行是说明了要写一行新的数据了。

不过这里看不出啥端倪来,我们借助 mysqlbinlog 工具来看看是否有新的发现。

为了查看 binlog,MySQL 为我们提供了两个官方工具,除了上面的 show binlog events,另一个就是 mysqlbinlog 命令,如下(注意在系统中执行该命令,不是在 MySQL 终端执行该命令):

mysqlbinlog -vv /var/lib/mysql/javaboy_logbin.000002 --start-position=794;

-vv 表示显示详细信息,这样就会打印出 binlog 中二进制文件的内容。

MySQL 主从复制数据不一致的解决方法

这里的内容比较多,我们来看几个比较关键的地方:

  • Table_map: javaboy_db.user mapped to number 108:这表示接下来要操作编号为 108 的表,每张表都有一个自己的编号。
  • Write_rows: table id 108 flags: STMT_END_F:这个就是具体的添加操作了,向编号为 108 的表中添加一条记录。

接下来那两行,大致上瞅一眼,像是 Base64 转码后的内容,大家感兴趣的可以自行解码看看,解码后有一些是乱码的,但是有一些字符串如 uuid 则没有乱码,我们也能大致猜出来这里存储的内容。

接下来我们看下面记录的 SQL,如下:

MySQL 主从复制数据不一致的解决方法

这就是日志中记录的内容,可以看到,每个字段上具体的值是啥,都写下来了,这样当然就不会发生数据不一致的情况了。

5. 小结

到此这篇关于MySQL 主从复制数据不一致的解决方法的文章就介绍到这了,更多相关MySQL 主从复制数据不一致内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL数据迁移相关总结
Apr 29 MySQL
MySQL 自定义变量的概念及特点
May 13 MySQL
MySQL 外键约束和表关系相关总结
Jun 20 MySQL
MySQL query_cache_type 参数与使用详解
Jul 01 MySQL
为什么MySQL选择Repeatable Read作为默认隔离级别
Jul 26 MySQL
一文带你探究MySQL中的NULL
Nov 11 MySQL
MYSQL如何查看进程和kill进程
Mar 13 MySQL
MySQL Server层四个日志的实现
Mar 31 MySQL
mysql如何查询连续记录
May 11 MySQL
mysql实现将字符串字段转为数字排序或比大小
Jun 14 MySQL
mysqldump进行数据备份详解
Jul 15 MySQL
MySQL表字段数量限制及行大小限制详情
Jul 23 MySQL
浅谈redis的过期时间设置和过期删除机制
MySQL读取JSON转换的方式
Mar 18 #MySQL
分享MySQL常用 内核 Debug 几种常见方法
Mar 17 #MySQL
MySQL如何快速创建800w条测试数据表
Mar 17 #MySQL
利用JuiceFS使MySQL 备份验证性能提升 10 倍
MySQL 分区表中分区键为什么必须是主键的一部分
MySQL优化及索引解析
You might like
人大复印资料处理程序_补充篇
2006/10/09 PHP
Zend Studio for Eclipse的java.lang.NullPointerException错误的解决方法
2008/12/06 PHP
第四章 php数学运算
2011/12/30 PHP
discuz加密解密函数使用方法和中文注释
2014/01/21 PHP
PHP的一个完美GIF等比缩放类,附带去除缩放黑背景
2014/04/01 PHP
PHP使用http_build_query()构造URL字符串的方法
2016/04/02 PHP
Windows下php+mysql5.7配置教程
2017/05/16 PHP
改进:论坛UBB代码自动插入方式
2006/12/22 Javascript
JavaScript高级程序设计 读书笔记之八 Function类及闭包
2012/02/27 Javascript
jquery 中多条件选择器,相对选择器,层次选择器的区别
2012/07/03 Javascript
图标线性回归斜着移动到指定的位置
2013/08/16 Javascript
$("").click与onclick的区别示例介绍
2014/09/25 Javascript
Javascript学习笔记之数组的遍历和 length 属性
2014/11/23 Javascript
浅谈javascript构造函数与实例化对象
2015/06/22 Javascript
JS实现从网页顶部掉下弹出层效果的方法
2015/08/06 Javascript
javascript中caller和callee详解
2015/08/10 Javascript
基于JavaScript实现移除(删除)数组中指定元素
2016/01/04 Javascript
下一代Bootstrap的5个特点 超酷炫!
2016/06/17 Javascript
Node.js使用MySQL连接池的方法实例
2018/02/11 Javascript
深入理解Vue Computed计算属性原理
2018/05/29 Javascript
微信小程序中插入激励视频广告并获取收益(实例代码)
2019/12/06 Javascript
Python格式化css文件的方法
2015/03/10 Python
Python文件操作,open读写文件,追加文本内容实例
2016/12/14 Python
Python多线程中阻塞(join)与锁(Lock)使用误区解析
2018/04/27 Python
Django框架设置cookies与获取cookies操作详解
2019/05/27 Python
wxpython绘制音频效果
2019/11/18 Python
Python之——生成动态路由轨迹图的实例
2019/11/22 Python
茵宝(Umbro)英国官方商店:英国足球服装生产商
2016/12/29 全球购物
TUMI新加坡官网:国际领先的商旅箱包品牌
2019/01/12 全球购物
自荐信怎么写好
2013/11/11 职场文书
雷人标语集锦
2014/06/19 职场文书
大学开学典礼新闻稿
2015/07/17 职场文书
2019年教师节:送给所有老师的祝福语
2019/09/05 职场文书
JavaWeb实现显示mysql数据库数据
2022/03/19 Java/Android
css中有哪些方式可以隐藏页面元素及区别
2022/06/16 HTML / CSS
JavaScript圣杯布局与双飞翼布局实现案例详解
2022/08/05 Javascript