MySQL的诡异同步问题

近期遇到一个诡异的MySQL同步问题,经过多方分析和定位后发现居然是由于备份引发的,非常的奇葩,特此记录一下整个问题的分析和定位过程。

现象

同事扩容的一台slave死活追不上同步,具体的现象是SBM=0,但是Exec_Master_Log_Pos执行的位置和Read_Master_Log_Pos完全对不上,且服务器本身CPU和IO都消耗的非常厉害。

——total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--

usr sys idl wai hiq siq| read writ| recv send| in out | int csw

     |8667B 862k|   |   |  

     |  102M| 112k 12k|   | 21k 50k

     |  101M| 89k 5068B|   | 21k 49k

     |  103M| 97k 5170B|   | 21k 50k

     |  103M| 80k 15k|   | 21k 50k

     |  102M| 112k 8408B|   | 21k 50k

分析

由于SBM并不能真正代表同步的状态,所以最开始我们认为有一个比较大的event在执行过程中,所以导致SBM=0但是pos位置不对,但是在观察一段之后发现,现象并没有变化,并且Relay_Log_File始终在同一个文件上。

为了找到这个现象的原因,我们使用strace来分析。由于服务器的现象是有非常大的磁盘写操作,故先使用iotop定位写入量较大的thread id,然后通过strace -p来分析。

通过strace采集的信息如下:

read(, "376bin>303275T1721325322Bg000k0000040005.5.31-"..., ) = 

lseek(, , SEEKSET) = 

write(, "./relay-bin.000914n107nmysql-bin"..., ) = 

read(, "", ) = 

close() = 

lseek(, , SEEKSET) = 

write(, "./relay-bin.000914n107nmysql-bin"..., ) = 

unlink("./relay-bin.rec") = - ENOENT (No such file or directory)

open("./relay-bin.rec", ORDWR|OCREAT, ) = 

lseek(, , SEEKCUR) = 

fsync() = 

lseek(, , SEEKSET) = 

read(, "./relay-bin.000914n./relay-bin.0"..., ) = 

lseek(, , SEEKSET) = 

write(, "./relay-bin.000914n./relay-bin.0"..., ) = 

lseek(, , SEEKSET) = 

read(, "", ) = 

lseek(, , SEEKEND) = 

fsync() = 

lseek(, , SEEKSET) = 

close() = 

unlink("./relay-bin.rec") = 

lseek(, , SEEKSET) = 

read(, "./relay-bin.000914n./relay-bin.0"..., ) = 

open("./relay-bin.000914", ORDONLY) = 

lseek(, , SEEKCUR) = 

read(, "376bin>303275T1721325322Bg000k0000040005.5.31-"..., ) = 

lseek(, , SEEKSET) = 

write(, "./relay-bin.000914n107nmysql-bin"..., ) = 

read(, "", ) = 

从上面可以看出MySQLD在不断的read和write353638这3个文件。通过内容,我们大概可以看出这3个文件应该是relay-bin.index、relay-bin.000914和relay-log.info这3个文件。但是如何确定这3个文件呢? 这时候就需要lsof了,通过lsof我们可以发现每个pid在proc下都会有一个fd的目录,在这个目录中就是各个文件的链接,通过fd号就可以直接确定具体是那个文件了

cd /proc/$pid/fd

ll -h

lr-x------  root root  Jan  :  -> /dev/null

l-wx------  root root  Jan  :  -> /data1/mysql3361/error.log

lrwx------  root root  Jan  :  -> /data1/mysql3361/iblogfile1

lrwx------  root root  Jan  :  -> /data1/mysql3361/iblogfile2

lr-x------  root root  Jan  :  -> /data1/mysql3361/relay-bin.rec

lrwx------  root root  Jan  :  -> /data1/mysql3361/ibF0ovt9 (deleted)

lrwx------  root root  Jan  :  -> socket:

lrwx------  root root  Jan  :  -> /data1/mysql3361/relay-bin.index

lrwx------  root root  Jan  :  -> socket:

lrwx------  root root  Jan  :  -> /data1/mysql3361/sngdb/sngrankscore6.ibd

lrwx------  root root  Jan  :  -> /data1/mysql3361/mysql/host.MYI

lrwx------  root root  Jan  :  -> /data1/mysql3361/mysql/host.MYD

l-wx------  root root  Jan  :  -> /data1/mysql3361/error.log

lrwx------  root root  Jan  :  -> /data1/mysql3361/mysql/user.MYI

lrwx------  root root  Jan  :  -> /data1/mysql3361/mysql/user.MYD

定位

至此,我们已经非常确定是由于这3个文件导致,那么具体看一下这3个文件有什么异常?在挨个检查后发现,relay-bin.index文件非常异常,同样的relay-bin在index中存在了两行,这会不会就是问题的原因呢?

cat relay-bin.index

./relay-bin.

./relay-bin.

./relay-bin.

./relay-bin.

cat relay-log.info

./relay-bin.

mysql-bin.

人肉去掉一行之后,发现一切变正常了,那么看来问题的原因就在于relay-bin.index中存在着两行同样的记录导致的了,但是为什么会导致这种现象呢?

再分析

这时候纯想已经没有用了,只好从code中寻找答案,主要看slave.cc和log.cc。

具体来说就是MySQL在启动slave的时候会从relay-log.info中读取对应的filename和pos然后去execute relay log event,当执行完毕之后会进行删除操作,mysql会使用reinit_io_cache重置relay-log.index文件的文件指针,之后再采用find_log_pos里面的代码mcmcmp从relay-log.index第一行确认所需偏移,这时候就又回到了同样的relay-log,于是死循环产生了。

int MYSQLBINLOGfindlogpos(LOGINFO linfo, const char logname,

                bool needlock)

(void) reinitiocache(&indexfile, READCACHE, (myofft) 0, 0, 0);

  for (;;)

    if (!logname

    (lognamelen == fnamelen-1 && fullfnamelognamelen == 'n' &&

     !memcmp(fullfname, fulllogname, lognamelen)))

      DBUGPRINT("info", ("Found log file entry"));

      fullfnamefnamelen-1]= 0;           // remove last n

      linfo->indexfilestartoffset= offset;

      linfo->indexfileoffset = mybtell(&indexfile);

      break;

    }

    }

  }

}

  

简单来说:

  1. sql-thread读取relay-log.info,开始从000914开始执行。
  2. 执行完毕之后,从index中读取下一个relay-log,发现还是000914
  3. 于是将指针又定位到index文件的第一行
  4. 然后死循环就产生了
  5. 至于之后的000915等文件是由于io-thread生成的,和sql-thread无关,可以忽略

至此,出现SBM=0现象,并且产生很大的IO消耗的原因已经确定,就是由于relay-bin.index文件中的重复行导致。但是为什么会产生同样的两行数据呢?

分析产生原因

首先,这个绝对不可能是人闲的抽风手动vi的。

其次,手动执行flush logs的时候relay-log会自动增加一个。

再次,众所周知,MySQl再启动的时候会自动执行类似flush logs的操作,将relay-log自动向下创建一个。

结合上面的信息,并在同事的不断追查下,最终发现是由于我们的备份导致的。

第一、我们每天都会进行数据库的冷备,使用rsync方式。

第二、我们每天会对日志进行归档操作,定时执行flush logs。

当以上两个操作遭遇到一起之后,就会发生在拷贝完relay-log和relay-bin.index之间执行了flush logs操作,这样就导致relay-log和relay-bin.index中不等,也就导致了使用这个备份进行扩容都会产生index文件中的重复行。

总结改进

终于,这么奇葩,这么小概率的一个问题终于找到根源,在此之前,真是想破头也想不出来到底是怎么回事。感谢 @zolker @大自在真人 @zhangyang8的 深入调研和追查,没有锲而不舍的精神,我们是不会追查到这种地步的。

然后就是改进了:

  1. 加强对备份的文件一致性效验,从根源上避免。
  2. 加强还原程序的兼容性,由于index文件对于扩容的slave并没有实际意义,所以如果发现重复行可以直接置空index文件。

MySQL的诡异同步问题-重复执行一条relay-log的更多相关文章

  1. 执行一条cmd命令的window.bat 批处理代码:

    . .执行一条cmd命令的window.bat 批处理代码: @echo off echo NodeJS SUPERVISOR...Server.js ::下面是批处理代码 supervisor d: ...

  2. MySQL复制报错(Slave failed to initialize relay log info structure from the repository)

    机器重启以后,主从出现了问题,具体报错信息: Slave failed to initialize relay log info structure from the repository 解决方案: ...

  3. 执行一条sql语句update多条记录实现思路

    如果你想更新多行数据,并且每行记录的各字段值都是各不一样,你会怎么办呢?本文以一个示例向大家讲解下如何实现如标题所示的情况,有此需求的朋友可以了解下 通常情况下,我们会使用以下SQL语句来更新字段值: ...

  4. 执行一条sql语句update多条不同值的记录实现思路

    如果你想更新多行数据,并且每行记录的各字段值都是各不一样,你会怎么办呢?本文以一个示例向大家讲解下如何实现如标题所示的情况,有此需求的朋友可以了解下 通常情况下,我们会使用以下SQL语句来更新字段值: ...

  5. 执行一条SQL语句,插入多条数据!

    insert into blog (bid, aid) values (2,1)(2,2)(2,3)

  6. hive如何执行一条sql的例子

    SQL如何在Mapreduce执行 左边是数据表,右边是结果表,这条 SQL 语句对 age 分组求和,得到右边的结果表,到底一条简单的 SQL 在 MapReduce 是如何被计算, MapRedu ...

  7. Sql Server执行一条Update语句很慢,插入数据失败

    今天同事要我修改服务器数据库里面的2条数据,查看服务器上的SQL Server数据库的时候,发现这几天数据没有添加成功,然后发现磁盘很快就满了,执行Update语句时,执行半天都提示还在执行,查询语句 ...

  8. MySQL 主主同步

    双机热备的概念简单说一下,就是要保持两个数据库的状态自动同步.对任何一个数据库的操作都自动应用到另外一个数据库,始终保持两个数据库数据一致. 这样做的好处多. 1. 可以做灾备,其中一个坏了可以切换到 ...

  9. mysql单向自动同步

    mysql自动同步 以下教程均使用mysql自带的自动同步功能 全库单向自动同步 本例把192.168.3.45上名称为ewater_main的数据库自动同步到192.168.3.68的ewater_ ...

随机推荐

  1. Struts 2 拦截器

    什么是Struts 2 拦截器  拦截器就是当用户请求后台Action类时在Action的Excute()方法执行前和Result返回魔板试图之后(将页面(数据)发送给浏览器渲染之前)所需要的一些通用 ...

  2. IE11之F12 Developer Tools--DOM Explorer

    使用DOM Explorer工具查看网页的DOM状态.检查HTML结构和CSS样式,并测试更改以解决显示问题.这可以在元素位置错误或行为异常时帮助你诊断问题,然后解决问题. DOM Explorer图 ...

  3. 查找最小的k 个元素之C#算法实现

    紧接着上一篇微软编程面试100题,这次想解决的是查找最小的K个元素,题目是:输入n 个整数,输出其中最小的k 个.例如输入1,2,3,4,5,6,7 和8 这8 个数字,则最小的4 个数字为1,2,3 ...

  4. AJAX使用技巧:如何处理书签和翻页按扭

    本篇文章提供了一个开源JavaScript库,它提供了给AJAX应用程序中添加书签和会退按钮的功能.在学习完这个教程后,开发者将能够对开发AJAX应用碰到的问题获得一个解决方案,这个特性甚至Googl ...

  5. 容器--Collection和AbstractCollection

    一.前言 容器是JAVA中比较重要的一块,整个体系设计得非常好,同时对于代码学习来说也是比较好的范例.同时很多面试官也比较喜欢用容器来考察面试者的基础知识,所以掌握好容器还是比较重要的.本文主要总结一 ...

  6. Spring框架之AOP

    SpringAop: 1.加入 jar 包 com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver ...

  7. ahjesus js 快速求幂

    /* 快速幂计算,传统计算方式如果幂次是100就要循环100遍求值 快速幂计算只需要循环7次即可 求x的y次方 x^y可以做如下分解 把y转换为2进制,设第n位的值为i,计算第n位的权为x^(2^(n ...

  8. springmvc+mybatis+spring 整合

    获取[下载地址]   [免费支持更新]三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体[新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统] ...

  9. winform里面网页显示指定内容

    今天有个同事问了一下我,怎么在winform里面打开网页啊?我们都是基于C/S的开发,很少接触winform,所以我当时就懵了,实在不知道怎么回答,所以索性说不知道.但是我又想了想,这个应该是个很简单 ...

  10. Android5.0新特性——新增的Widget(Widget)

    新增的Widget RecyclerView RecyclerView是ListView的升级版,它具备了更好的性能,且更容易使用.和ListView一样,RecyclerView是用来显示大量数据的 ...