当前位置: 首页 > 后端技术 > Java

MySQL主从复制数据不一致,怎么办?

时间:2023-04-01 16:35:10 Java

@[toc]今天的文章来晚了。主要是我一觉醒来黄码变了。关键是我还不知道。早上8点20分到公司楼下,保安要查健康码。当我满怀信心的时候,打开广东省事我傻眼了。折腾了一上午,绿码终于回来了。人生本就充满惊喜。..书接上次,八卦不表。今天我们就来说说MySQL的主从复制数据不一致的问题。通过几个具体的案例,给小伙伴们看看不同格式的binlog的区别。一、准备工作以下配置基于Docker。这里我用一张简单的图给大家看下MySQL主从是如何工作的:在这里,我们准备两台机器:host:10.3.50.27:33061slave:10.3.50.27:330621.1hostconfigurationhost的配置分三步,就是比较容易:1.授权给从服务器GRANTREPLICATIONSLAVEON*.*to'rep1'@'10.3.50.27'identifiedby'123';冲洗特权;这里表示配置slave登录用户名为rep1,密码为123,必须从地址10.3.50.27登录。登录成功后,可以对任意库中的任意表进行操作。其中,如果不需要限制登录地址,可以用%代替IP地址。请注意,在MySQL8中,这方面有一些变化。在MySQL8中,用户的创建和授权需要分开,不能像上面那样一步到位。具体方法如下:CREATEUSER`rep1`@`10.3.50.27`IDENTIFIEDWITHcaching_sha2_passwordBY'javaboy.COM';GRANTReplicationSlaveON*.*TO`rep1`@`10.3.50.27`;2、修改主库配置文件开启binlog,并设置server-id。每次修改配置文件后,都必须重启MySQL服务才能生效。启用binlog主要是修改mysql的配置文件mysqld。cnf,文件在容器的/etc/mysql/mysql.conf.d目录下。对于这个配置文件,我们做如下修改:[mysqld]#该参数表示开启binlog功能,指定binlog存放目录log-bin=javaboy_logbin#设置binlog_format格式binlog_format=STATEMENT#设置最大字节一个binlog文件的#SetMaximum100MBmax_binlog_size=104857600#设置binlog文件的有效期(单位:天)expire_logs_days=7#binlog日志只记录指定库的更新(配置主从复制时使用)binlog-do-db=javaboy_db#binlog日志不记录指定库的更新(配置主从复制时会用到)#binlog-ignore-db=javaboy_no_db#多少次写入缓存,刷新磁盘once,默认0表示这个操作由操作系统根据自身负载决定多久写一次Disk#1表示每次事务提交都会立即写入磁盘,n表示n次事务提交都会写入tothedisksync_binlog=0#为当前服务取一个唯一的id(MySQL5.7要求)server-id=1items配置松哥的意思在Gaze中已经解释过了。截图如下:如下图所示:log-bin:同步日志的路径和文件名。一定要注意这个目录如果MySQL有写权限(这里我偷懒了,直接放在下面的datadir下)。binlog-do-db:要同步的数据库名称。当slave连接master时,只会同步这里配置的数据库,其他的不会同步。server-id:mysql在master-slave环境下的唯一标识,随便给一个数字,注意不要和slave一样。修改binlog_format的值为STATEMENT很关键。配置完成后,重启MySQL服务器:dockerrestartmysql330613。查看主服务器当前的二进制日志名称和偏移量。这个操作的目的是从数据库启动后从这个点开始恢复数据:showmasterstatus;再看看binlog_format设置是否成功:可以看到,没问题。至此,主机配置完成。1.2从机配置从机的配置也比较简单。下面一步步来看:1.在/etc/my.cnf中添加配置注意从机这里只需要配置server-id即可。注意:如果从机是从宿主机复制过来的,也就是我们通过复制CentOS虚拟机得到MySQL实例,此时两个MySQL的uuid是一样的(正常安装不会一样),那么就需要手动修改修改位置在/var/lib/mysql/auto.cnf中,注意这里修改几个字符,但不要太随意,比如修改uuid的长度。配置完成后记得重启slave。2、使用命令配置slave更改master为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,完整的命令是如下:修改master为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进程startslave;启动后检查slave状态:showslavestatus\G;4.查看slave的状态。下面两个值必须是YES,说明配置正确:Slave_IO_Running:YesSlave_SQL_Running:Yes至此配置完成,master创建数据库,添加数据,slave会自动同步。如果这两个中有一个不是YES,则说明主从环境搭建失败。这时候可以通过阅读日志查看错误原因,再针对具体问题进行解决。具体的同步过程如下:首先,通过从机33062上的changemaster命令,设置宿主机33061的IP、端口、用户名、密码,以及从哪里开始请求binlog(master_log_pos),其中包括文件名和日志偏移量。在slave机器33061上执行startslave命令,此时slave机器会启动两个线程,分别是io_thread和sql_thread。io_thread负责与主机建立连接。host33061验证用户名和密码后,开始根据slave33062传过来的location,从本地读取binlog,发送给33062,slave33062拿到binlog后,写入本地文件,称为中继日志。sql_thread线程读取传输日志,解析出日志中的命令,并执行。大致是这样一个过程。2.数据不一致的问题接下来我们创建一个javaboy_db数据库,并在里面创建一个user表。用户表的定义如下:CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`uuid`varchar(128)DEFAULTNULL,`name`varchar(64)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDB默认字符集=utf8mb4;接下来,我们在宿主机的用户表中插入一条记录,如下:,这条记录会同步到从机33062:可以看到,确实同步了数据,只是uuid不一样。3、原因分析我们知道,MySQL主从同步的主要依据是binlog。master将自己的binlog发送给slave,slave重放后得到与master相同的数据。那我们来看看master生成的binlog长什么样子。我们来看事件形式的binlog。命令格式如下:showbinlogevents[IN'log_name'][FROMpos][LIMIT[offset,]row_count];意思是以事件的形式查看binlog,涉及到几个参数:log_name:可以指定要查看的binlog日志文件的名称。如果不指定,表示查看最早的binlog文件。pos:从哪个pos点开始查看。所有记录在binlog中的操作都有一个pos点。这实际上相当于我们可以指定从哪个操作开始查看日志。如果不指定,就是从binlog的开头开始查看。offset:这个是偏移量,如果不指定,默认为0。row_count:查看多少行记录,如果不指定,表示查看全部。查看命令如下(我是从pos为154的位置开始):showbinlogeventsIN'javaboy_logbin.000001'FROM154;查看结果如下(部分):从图中可以看出,原始binlog中记录的日志为:usejavaboy_db;插入用户(uuid,名称)值(uuid(),'javaboy')。这条SQL语句以后同步到slave后,如果slave照着执行,难免会出现执行结果不一致的问题,因为uuid()函数每次执行的结果都不一样。现在你们可以看到问题的原因了。4.问题解决问题很容易解决。我们在上一篇文章中说过,我们可以将binlog_format设置为ROW来解决这个问题。具体操作步骤如下。在宿主机中,修改/etc/mysql/mysql.conf.d/mysqld.cnf配置文件,将binlog_format改为ROW,如下:修改完成后,重启宿主机。主机重启后,会生成一个新的binlog文件,所以我们需要查看主机的最新状态,重新配置slave。先看host,如下:在此基础上让slave重新连接host,然后对slave进行如下操作:stopslave;changemastertomaster_host='10.3.50.27',master_port=33061,master_user='rep1',master_password='123',master_log_file='javaboy_logbin.000002',master_log_pos=794;启动奴隶;重新配置slave后,我们继续往user表中插入一条数据,插入完成后,我们查看slave的数据,发现此时的数据是一致的。为了解决这个问题,我们主要的改动是将binlog_format改为ROW。我们将binlog_format改为ROW后,我们来看看此时binlog中记录了什么。显示binlog事件IN'javaboy_logbin.000002'FROM794;可以看到,BEGIN和COMMIT之间是我们的数据修改操作。table_map:这一行是说明接下来要操作的javaboy_db.user表。Write_rows:这一行表示要写入新的一行数据。但是这里没有头绪,我们用mysqlbinlog工具看看有没有新的发现。为了查看binlog,MySQL官方为我们提供了两个工具。除了上面的showbinlogevents,另一个就是mysqlbinlog命令,如下(注意该命令是在系统中执行的,不是在MySQL终端中执行的):mysqlbinlog-vv/var/lib/mysql/javaboy_logbin.000002--start-position=794;-vv表示显示详细信息,会打印出binlog中二进制文件的内容。这里内容很多,我们来看几个重点:Table_map:javaboy_db.user映射到108号:意思是接下来要操作编号为108的表,每个表都有自己的编号。write_rows:tableid108flags:STMT_END_F:这个是具体的加法操作,在编号为108的表中增加一条记录。大致看下两行。看起来是Base64转码后的内容。有兴趣的可以自己解码。解码后,有的是乱码,但是uuid等一些字符串没有乱码。我们也可以大致猜到这里存储的是什么。接下来我们看下面记录的SQL,如下:这是日志中记录的内容。可以看到每个字段的具体值都记下来了,当然不会出现数据不一致的情况。5.总结。今天通过一个简单的案例,给小伙伴们分享了binlog的两种不同的日志格式。还有一种现在很少使用的MIXED格式。有兴趣的朋友可以结合上一篇文章。内容,在本文案例的基础上继续测试MIXED模式,这里不再赘述~