警报响起:
Last_SQL_Errno: 1062
Last_SQL_Error: Error 'Duplicate entry '4294967295-2147483647-825241648' for key 'law_rea_pec'' on query. Default database: 'fuck_cn'. Query: 'INSERT INTO `u_law_pec` (`law_id`,`rea`,`pec`,`pec_sub`,`amount`,`stat`,`input_time`) VALUES (0x323036363434,0x323631333030,0x31303030,0x313030312C313030322C313030332C313030342C313030352C31303036,0x30,0x30,0x31343239383639323236)'
数据库同步挂了,整个体系瘫痪。
看到是有重复的值导致的,首先就跳过了一条记录。 再启动同步,可以同步了。ok
不一会 警报又响起了,还是一样的错误。
仔细一看 4294967295 已经是 int 无符号整形的最大值了。 INSERT语句里面还一堆的十六进制的字符串。
这个十六进制是怎么来的呢?
如是查查日志,发现日志里面也是十六进制的,可以确认是PDO 连接类发过来的语句就是这样了。
首先是预处理,然后执行SQL语句,查询日志如下:
Prepare INSERT INTO `u_law_pec` (`law_id`,`rea`,`pec`,`pec_sub`,`amount`,`stat`,`flag`,`input_time`) VALUES (?,?,?,?,?,?,?,?)
Execute INSERT INTO `u_law_pec` (`law_id`,`rea`,`pec`,`pec_sub`,`amount`,`stat`,`flag`,`input_time`) VALUES (0x3330333034373335,0x313730333030,0x31323030,"",0x30,0x30,0x32,0x31343330383034343336)
通过这样去查看这些十六进制: 发现是可以正确解码的。
mysql> select 0x323036363434,0x323631333030,0x31303030,0x313030312C313030322C313030332C313030342C313030352C31303036,0x30,0x30,0x31343239383639323236;
+----------------+----------------+------------+--------------------------------------------------------------+------+------+------------------------+
| 0x323036363434 | 0x323631333030 | 0x31303030 | 0x313030312C313030322C313030332C313030342C313030352C31303036 | 0x30 | 0x30 | 0x31343239383639323236 |
+----------------+----------------+------------+--------------------------------------------------------------+------+------+------------------------+
| 206644 | 261300 | 1000 | 1001,1002,1003,1004,1005,1006 | 0 | 0 | 1429869226 |
+----------------+----------------+------------+--------------------------------------------------------------+------+------+------------------------+
但是 INSERT 这个语句的时候发现写入的数据不对:
0x323036363434 对应的是 206644 却写入 4294967295
猜测是数据溢出了。
0x323036363434 <16进制数值转10进制数值是> 55182649340980 远大于了INT 的最大值。
问题就在这里了:
本来是需要十六进制转字符串文本的,结果mysql在这里把十六进制转成了十进制的数值。
因为表达式 x'test-string' (4.0 中新加入) 是基于 ANSI SQL 的,表达式 0x 是基于 ODBC 的, 所以我们没有使用ODBC的方式操作数据库的时候,就出现了上面的转储不正确的情况。
解决问题方法:
1. PDO 传过来的数据用标准sql语句的文本格式,别搞些十六进制的字符串。
通过对比实验发现 :
Mysql5.6版本, PHP5.6 版本的 使用PDO 是不会出现上面的情况的。之前用的低版本的PHP带PDO 操作数据就会出现上面的问题。 所以 我们采用升级PHP版本的方式 解决这个问题。
--------------------------------------
今天发现之前也有路人甲遇到过此类问题并且分析的比较详细:
访问 mysql server 的方式有两种。
1、 直接访问模式
2、 预处理模式
先说说这两个结构的区别,直接访问就像我们用客户端连接进数据那样,标准的 sql 语句插入、更新、删除和查询。这个要求就是每个命令里面都要指明 表、字段、等信息。
预处理就是:先告诉 mysql 一个表的结构,然后,后面的全都按照这个表结构来,这样就不用每次都发送 表、字段等信息了。这样的优势是大量的插入会快一点。特点是只在第一次发送表结构。而不是每次都发送一遍,问题是 mysql binlog 里面不支持这种格式。
这两种方式比较起来,第一种 安全,第二种 快速 。第二种因为没有表结构,所以当任何一个字段出现问题,就会造成所有的数据问题,而不像第一种,只影响那一句。那个 PDO 使用的就是第二种方式,而且他错误的认为一切都是字符串,把所有的数据都转换成 16 进制了。
在第一次插入的时候 mysql 使用第二种方式插入数据,但 binlog 里面因为没有这种结构,所以他自己把语句转换成了 第一种模式,加上了表、及字段信息,但 mysql 不会对 int 形做相应的转换,(这个在字符串表示中是没有错误的),造成了记录的日志是按照字符串的方式记录的。这样在吧一个字符串插入 int 形就出现了插入的数据和日志不一致的情况。要解决这个问题只有 1、给mysql 写一个补丁,解决这个问题。(现在功力不够还写不出来) 2、在我们公司禁用 预处理结构体方式的数据写入。看来目前我们只能使用第二种方法了。
结论:
总的来说,pdo 写的有问题,mysql 的 log 记录转换的方式也存在问题。下面是我写的一个能够触发这个 bug 的代码。
原文:http://blog.csdn.net/ugg/article/details/9042255
-------------------------------------------------