两个不同的字符串,分别插入一个表,保存字符串的字段叫 word,在 word 上建了唯一索引(unique key)。
连续插入这 2 个字符串,第二个居然报错:Duplicate entry ‘%s’ for key %d – Error:
SQLSTATE:
(
)
是索引存在,不能插入 2 条相同 word 的记录。
这就奇怪了,明明是 2 个不同的字符串啊,urlencode() 和 md5() 计算出来的结果都不一样,怎么就不能插入呢?
后来仔细对比,并查了 ASCII 码表,发现有些字符经 urlencoe 以后,有一个字节是 %93,或者其他的,反正都大于 0x80 了。
如果这个字节加上后面的一个字节,不是一个有效的 GBK 编码,那么,MySQL 会把字符串截断到这个字节之前。
我做了一个测试,两个字符串经 urlencode 编码后是这样的:
‘%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F’
‘%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F%93%3F%3F%94%3F%3F%3F%3F%3F%3F’
在一个表建立唯一索引,然后按照先插入第一个字符串,再插入第二个,肯定会报错(GBK编码的数据库、表和字段)。
分析原因:
这段时间在钻研 mysql_real_escape_string() 与 addslashes() 等函数的内在区别。
mysql_real_escape_string() 需要数据库连接句柄作为第二个参数,也就是说,这个函数需要参考当前连接的字符集进行转义。
GBK 编码,汉字是双字节的,第一个字节的 ASCII 码必须要大于 0x80,而第二个字节必须满足另一个规律才可以。这个规律就是 GBK 的字符集。第一个字节大于 0x80 的双字节字符有 128 * 256 个,但并不是说 GBK 的字符集就有 32768 个字符,我们的汉字好像没有这么多。
mysql_real_escape_string() 函数会参考当前连接的字符集,检查需要转义的字符串的每一个字节。
因此,如果一个汉字是 %bf%5c,并不会被转义成 %bf%5c%5c (addslashes() 的结果就是这样,\ 的ASCII 码就是 %5c),因为 %bf%5c 是一个有效的汉字。
我写了一个Wiki,里面是测试代码,用于验证上面的唯一索引的例子: