MySQL 字符集

两个不同的字符串,分别插入一个表,保存字符串的字段叫 word,在 word 上建了唯一索引(unique key)。

连续插入这 2 个字符串,第二个居然报错:Duplicate entry ‘%s’ for key %d – Error:

1062

SQLSTATE:

23000

(

ER_DUP_ENTRY

)

是索引存在,不能插入 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,里面是测试代码,用于验证上面的唯一索引的例子:

http://wiki.leakon.com/MySQLUniqueKeyCharset

Leave a Reply

Your email address will not be published.

*