一、问题起源 不久前用户反馈部门的MySQL数据库发生了数据更新丢失。为了解决这个问题,当时对用户使用的场景进行了分析。发现可能是因为用户在两台互为主从的机器上都进行了写入导致的数据丢失。 如图所示,是正常和异常情况下应用写入数据库的示例。随后在更加深入调查问题的过程中,DBA发现了故障引起数据丢失的原因: 如图1-2所示为故障具体过程的还原。从图中可以看出在第3步DP上的写入操作,在恢复DA到DP的同步之后,覆盖了第4步DA上的写入。因此导致了最终两台机器数据不一致,并且有一部分数据更新丢失。 在这里相信读者都会有一个疑问,在第4步之后数据变成了(id:1,name:name4),那么第3步操作的时候写入的语句是updatetsetname=name3whereid=1andname=name2,在第5步恢复同步的时候这条语句在DA上重放应该不会被成功执行,毕竟Where条件都不匹配了。而且在DP产生的Binlog中,确实也记录了SQL语句的Where条件,无论从哪个角度上来看第3步的SQL语句都不应该被重放成功。 ###UPDATE`test`.`t`###WHERE### 1=1/*INTmeta=0nullable=0is_null=0*/###2=name2/*VARSTRING()meta=nullable=1is_null=0*/###SET###1=1/*INTmeta=0nullable=0is_null=0*/###2=name3/*VARSTRING()meta=nullable=1is_null=0*/#at那么这个问题难道是MySQL自身的Bug,抑或是MySQL在某些特殊参数或者条件下的正常表现?对于这个问题,本文将可能的给出这个问题的详细解释和分析。二、Row格式下RelayLog的重放2.1BEFORIMAGEAFTERIMAGEbinlog_row_image参数 在最后解释本文最初提出的问题前,需要先来看下RelayLog是怎么被重放的。一般情况下,当有DML语句变更数据库中的数据的时候,Binlog会记录下事件描述信息、BEFOREIMAGE和AFTERIMAGE等信息。在这里有一个概念BEFOREIMAGE和AFTERIMAGE需要先介绍下: 1.BEFOREIMAGE:前镜像,既数据修改前的样子。 2.AFTERIMAGE:后镜像,既数据修改后的样子。 为了方便理解,这里贴一个Binlog的例子。假设当前有表t,然后表中数据如下: mysqlselect*fromt;+----+-------+ id name +----+-------+ 1 name4 +----+-------+1rowsinset(0.00sec) 之后执行SQL语句updatetsetname=1whereid=1; mysqlupdatetsetname=1whereid=1;QueryOK,1rowaffected(0.00sec)Rowsmatched:1Changed:1Warnings:0 然后来看下Binlog中的记录: #:28:28serveridend_log_posCRCxe4dedec0Update_rows:tableidflags:STMT_END_F###UPDATE`test`.`t`###WHERE### 1=1/*INTmeta=0nullable=0is_null=0*/###2=name4/*VARSTRING()meta=nullable=1is_null=0*/###SET###1=1/*INTmeta=0nullable=0is_null=0*/###2=1/*VARSTRING()meta=nullable=1is_null=0*/#at可以见得,在修改之前name字段的值是name4,在Binlog中用Where条件 2=name4来指明,而修改后的name的值是1,在Binlog中就是2=1来指明。因此BEFOREIMAGE就是Binlog中WHERE到SET的部分。而AFTERIMAGE就是SET之后的部分。那么DELETE,UPDATE和INSERT语句被记录在Binlog中的时候,是否都有BEFOREIMAGE和AFTERIMAGE?其实不是所有的DML事件类型都拥有两个IMAGE的,参见图2-2可知只有UPDATE语句,会同时拥有BEFOREIMAGE和AFTERIMAGE。 BEFORIMAGE和AFTERIMAGE默认会记录所有的列的变更,因此会导致Binlog的内容变得很大。那么有没有参数可以控制IMAGE(对于BEFORIMAGE和AFTERIMAGE以下合并简称为IMAGE)的行为?MySQL5.7之后引入了一个新的参数binlog_row_image。 参数说明: binlog_row_image:
|