PDO::ATTR_EMULATE_PREPARES属性设置为false引起的血案

前段时间给pdo设置了下emulate_prepare属性,引起了此次的血案。在这记录下事情的通过,没准你们能避免一样的错误。php

先说如下环境。php 5.2.5,mysql 5.0.81,服务器使用的GBK编码。mysql

原由

   首先是看到一个报错信息,说是sql语句的syntax error。这里给一个能重现的例子。报错的位置就是在红框的位置。sql

排查

从上面看,看不出语法错误。然而GBK编码的“玕”字后一个字节是0x5c,跟“\”同样。看到这,是否是就以为这可能会有问题?可是,我记得咱们是使用了pdo的prepare这种方式的(上面sql语句中我使用实际的值代替了?占位符),即便这个汉子特殊,也不会报语法错误才对。因而我翻了翻代码,发现,果真没有显示的设置PDO::ATTR_EMULATE_PREPARES属性的值。而默认是设置为true的(从字面意思就能理解,模拟prepare,不是真正意义上的prepare执行方式)。数据库

使用tcpdump抓包,wireshark查看,肯定了一下,发现果真是上面这样。编程

抓包的结果,服务器

0xab5c是汉字玕的GBK编码。这里看到的是,pdo模拟处理后,多加了一个'\',而在这以前,已经告诉mysql server,客户端使用的是GBK编码(set names gbk),mysql server按着GBK字符集处理sql语句,多出来的转义符号,就形成了sql语句的语法错误。(后来查资料确认,这是pdo在处理多字符时候的一个bug,好像在php5.3.6,以后版本经过dsn里面设置charset能够解决,我没去确认从哪一个版本开始起做用的,可是5.4是能够起做用的。tcp

处理

既然pdo模拟prepare有这个问题,而真正意义的prepare既没有这个问题,又在防止sql注入上更胜一筹,那我就直接使用mysql提供的prepare执行方式就行了。因此,设置PDO::ATTR_EMULATE_PREPARES为false。测试发现,果真插入正常了。其余一切数据库操做貌似都正常。测试

惨啊

在我解决完问题挺高兴的时候,发现问题了。数据库主从同步出问题了。后来定位问题发现,mysql 的binlog里面sql语句里面的数据全都是转换到了ascii码的表示,例如 insert test (`id`) values(1),在binlog里面是insert test(`id`) values(0x31)。此时也才了解到mysql5.0不支持row同步方式,只支持语句的同步方式。binlog异常参见这篇文章http://backend.blog.163.com/blog/static/20229412620133274030845编码

总结

编程不容易,说多了都是泪。。努力,多学学吧。。spa

相关文章
相关标签/搜索