问题产生线上一直有个历史遗留问题,最近DBA提了出来,所以跟了下代码,作了下简单分析,问题描述如下:

在master-slave的环境下,对master上的某个表中的数据插入,会导致master-slave数据不一致的情况,通过反复试验,确定出现该情况的条件如下:

master上设置了character_set_server=gbk

应用中采用了prepared statement并且设置了useCursorFetch=true

对于master上某个表中的int字段,采用了字符的形式进行插入

比如,master上有张表,结构如下:

create table t(id intauto_increment primay key,count int)engine=innodb;

进行的操作如下:

conn =DriverManager.getConnection("jdbc:mysql://192.168.0.1:3307/test?useCursorFetch=true&user=root");

pstmt =conn.prepareStatement("insert into test(count) values(?)");

pstmt.setString(1,"1");

pstmt.execute();

通过上面的操作,我们发现,在master上,插入后的结果如下:

mysql>select*fromt;

+----+-------+

|id |count |

+----+-------+

|1|1|

+----+-------+

而在slave上,却变成了另外一个结果:

mysql>select*fromt;

+----+-------+

|id |count |

+----+-------+

|1|49|

+----+-------+

从上面的信息可以看出,master-slave上的数据出现了不一致,查看master上的binlog,我们会发现如下信息:

insert intot(count)values(0x31)

这里的binlog中的值‘1‘被转化成了16进制0x31。

问题分析binlog在某种情况下会被转化成16进制存储,这个可能很多人没有注意,转化成16进制有又造成了master-slave上数据不一致,这个就让人比较难以接受了,以下我们的分析就从这两方面入手:

MySQL为什么要把Binlog转化成16进制,在那些条件下会转成16进制?

转化成16进制后,数据为什么会出现不一致?

首先,我们来分析第一个问题,通过上面的条件进行跟踪测试,我们很快发现,MySQL把Binlog中的字符串转化成16进制存储的条件有两个:

客户端使用了prepared statement

客户端传过来的编码是多字节符集编码

客户端在使用prepared statement的时候,在执行前,需要先解析出内部给定的参数,由于涉及到转义的问题,需要把参数内部的如\0,\n等字符进行相应的替换(\0会被替换成‘\\0‘),而对于多字节字符集编码的字符串(如:gbk,gb2312),转义字符‘\‘可能出现在字符的第二个字节,例如字符串“?\0”,可能实际的编码是两个字符‘?\‘和‘0’,如果按照替换的原则替换,很有可能会破坏原有的字符串的内容,所以MySQL对于这种转义符‘\‘可能出现在第二个字节的字符集,都给了一个标识进行说明(escape_with_backslash_is_dangerous=1),在处理时,遇到这种字符集,直接把字符串转成16进制进行处理,这样就可以避免转义出现的问题。

具体的代码可以查看log_event.cc文件中的append_query_string函数,关键代码如下:

if(csinfo->escape_with_backslash_is_dangerous)

ptr=str_to_hex(ptr,from->ptr(),from->length());

else

{

*ptr++=‘\‘‘;

ptr+=escape_string_for_mysql(csinfo,ptr,0,from->ptr(),from->length());

*ptr++=‘\‘‘;

}

整个字符串转成16进制的过程在Prepared statement处理时进行,所以在没有使用prepared statement或者客户端字符集不会出现转义符‘\‘出现在第二字节的情况下,不会产生该问题。

转义符‘\‘会出现在第二字节的字符集如下:

my_charset_big5_chinese_ci

my_charset_big5_bin

my_charset_cp932_japanese_ci

my_charset_cp932_bin

my_charset_gbk_chinese_ci

my_charset_gbk_bin

my_charset_sjis_japanese_ci

my_charset_sjis_bin

分析了第一个问题,再来看第二个问题,binlog被转成16进制后,为什么主从上的数据会出现不一致?MySQL中,参数类型的确定在SQL语句的解析中进行,由于prepare statement采用先给出SQL后设置值得形式,所以在解析SQL时,MySQL还不知道具体的值,构造了Item_param对象,然后通过设置,把后面的参数‘1‘设置给Item_param的str_value成员,而对于salve,binlog传递没有prepared statement的信息,所以在slave上,还是按照一般的方式执行,slave发现value的值为0x31,所以构造一个Item_hex_string对象来保存,所以我们只需要查看下两个Item的save_in_field方法,就可以查明具体的原因,先看master上的处理:

field->store(str_value.ptr(), str_value.length(),str_value.charset());

最终的处理方式为

get_int(cs, from, len, &rnd, UINT_MAX32, INT_MIN32, INT_MAX32);

我们的字段类型为int,而实际给定的值是string,在处理时,需要进行转换,转换的关键代码如下:

for(ul=0;str

{

ul=ul *10+ch;

}

从上面的代码我们可以知道,master这种转化方式类似于C中的atoi函数,例如字符串‘1234‘会被转换为1234,如果中间出现非数字字符,后面部分会被截断,所以,在master上的数据就是字符串‘1‘转换过来的值1

现在看下slave上的处理:

slave上处理转换的方法主要在下面的代码:

nr= (ulonglong) val_int();

函数val_int的处理过程如下:

先检查字段类型,如果是string则按照string的方式处理,如果不是string,则按照下面的方式处理

char*end=(char*)str_value.ptr()+str_value.length(),

*ptr=end-min(str_value.length(),sizeof(longlong));

ulonglong value=0;

for(;ptr !=end;ptr++)

value=(value <<8)+(ulonglong)(uchar)*ptr;

slave上通过把value中的每个字节强制转化得到,所以如果value为‘1234’,通过强制转换过来的值将是:

(uchar)‘1‘<<24+(uchar)‘2‘<<16+(uchar)‘3‘<<8+(uchar)‘4‘

=31<<24+32<<16+33<<8+34

这样就造成了master-slave上的数据不一致

总结

该问题主要的原因在于MySQL的两种不同的Item在处理字符串转整型的方法不一致,Item_param通过类似于atoi的形式,直接把字符中的数字通过-‘0‘转换到整型,而Item_hex_string则通过强制内存转化所得,这两种方式都合理,但是两边没有统一,造成replication出错,MySQL从5.1版本后到目前MySQL5.5的版本中都存在该问题(MySQL5.6没有测试过,应该也存在该问题)。解决方法:

服务端使用utf8字符集编码(由于前面时gbk,改成utf8会出现乱码等很多问题)

更改应用不对int字段进行非int数据的插入

更改应用不使用prepare statement

binlog的format设置成row格式注:该问题已经上报给MariaDB,并且被确认为一个Bug,将在后续的版本中进行修复。

mysql主从字符集不一致_MySQL多字节字符集造成主从数据不一致问题相关推荐

  1. mysql数据库字符集作用_MYSQL数据库字符集支持

    MySQL4.1以前版本服务器只能使用单一字符集,从MySQL4.1版本开始,不仅服务器能够使用多种字符集,而且在服务器.数据库.数据表.数据列以及字符串常数多个级别上设置不同的字符集. 1.4.1. ...

  2. mysql 组复制 不一致_MySQL主从复制什么原因会造成不一致,如何预防及解决

    #目录MySQL主从复制什么原因会造成不一致,如何预防及解决? 你为什么会决定进行分库分表,分库分表过程中遇到什么难题,如何解决的? MySQL高可用架构应该考虑什么? 你认为应该如何设计? MySQ ...

  3. mysql 触发器 本表_MySQL触发器处理本表数据

    关于MySQL的触发器,基本上每个Mysql教程里都有讲到,但是我发现那些教程里讲的都是如何处理其他表的数据.在MySQL中写触发器操作 关于MySQL的触发器,基本上每个Mysql教程里都有讲到,但 ...

  4. mysql 字符集测试_MySQL多字符集备份恢复测试

    目的:测试单mysql实例在多字符集的数据库环境中备份恢复的乱码问题 准备工作: CREATE DATABASE `utf8_db` /*!40100 DEFAULT CHARACTER SET ut ...

  5. mysql数据库的字符集设置_mysql数据库字符集设置

    1. mysql UTF8设置 1)vi /etc/my.cnf [client] port = 3306 socket = mysql default-character-set=utf8 [mys ...

  6. mysql中设置字符集语句_mysql设置字符集

    一般情况下,防止程序交互过程中出现乱码情况,所以前后台都会同意编码格式. 因为UTF-8编码方式国际通用,所以我在mysql中设置编码格式UTF-8. 先查看mysql编码格式. show varia ...

  7. mysql设置字符集命令_mysql 修改字符集

    1: 在Mysql的配置文件 my.ini (一般在c:/windows/下面) 里加上服务器的默认编码配置: default-character-set=utf8 2: 修改你的数据库的字符编码:进 ...

  8. mysql主从 查询负载_MySQL集群:主从数据库配置 实现查询负载

    在做web应用系统中,如果数据库出现了性能瓶颈,而你又是使用的MySQL数据库,那么就可以考虑采用数据库集群的方式来实现查询负载了.因为一般来讲任何一个系统中数据库的查询操作比更新操作要多的多,因此通 ...

  9. mysql编译卡主_mysql 编译安装以及主从设定

    1. 下载MYSQL源码包至/home/mysql_home. 我下载下来的源码包文件名为:mysql-5.0.45.tar.gz 2. 在/home/mysql_home下,解压. tar zxvf ...

最新文章

  1. 如何分析802.11协议中的BA帧(block acknowledgement)
  2. 开心一刻,你和导师之间有什么有趣的事情?
  3. 固态硬盘量产工具_机械硬盘Q1出货量大降,电脑硬盘榜单出炉
  4. python利用百度云接口实现车牌识别
  5. 2020 Oracle JDK下载
  6. R语言学习笔记-Error in ts(x):对象不是矩阵问题解决
  7. 儿研所 计算机训练,金博智慧:注意缺陷、多动障碍儿童计算机认知训练(工作记忆训练)研究...
  8. 计算机断网后怎么连接网络连接,电脑突然断网, 连接不上网络怎么办? 三种方法轻松找回...
  9. 蓝湖(切图工具)插件的安装和使用
  10. 【不忘初心】经典珍藏 LTSB|1709|1809|LTSC 四版集合八合一[纯净精简版]2020.07.20
  11. excel 删除重复行数据,列数据
  12. 赫兹的单位换算_hz是什么单位(频率和赫兹的换算)
  13. Excel 文本转数值的方法
  14. git merge工具 meld
  15. 经典案例--JS购物车
  16. Linux添加环境变量,以配置MySQL环境怕变量为例
  17. 十分钟入门Pandas
  18. linux在vi创建文件,Linux下创建文本文件(vi/vim命令使用详解)
  19. Python Flask教程学习03
  20. php征婚段子,婚姻婚礼类阿金短视频脚本段子剧本台词范例抖音快手素材

热门文章

  1. 解决VS 于 致命错误 RC1015: 无法打开包含文件 #39;afxres.h#39; 问题
  2. 自己对多线程的一点思考
  3. linux 安装 nslookup
  4. python3 多维数组 NumPy ndarray 简介
  5. linux 用户身份与进程权限
  6. 项目代码结构 Dao,Service,Controller,Util,Model 含义
  7. python3 计时性能测试 %timeit %%timeit
  8. Linux下安装GCC5.3.0
  9. 用symbol来获得ShadowSSDT的原始地址和函数名
  10. git 和 vim 学习笔记