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

前段时间给pdo设置了下emulate_prepare属性,引发了这次的血案。在这记录下事情的经过,没准大家能避免同样的错误。

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

起因

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

排查

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

使用tcpdump抓包,wireshark查看,确定了一下,发现果然是上面这样。

抓包的结果,

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

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

处理

既然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

总结

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

你可能感兴趣的:(mysql,prepare)