您的位置:首页 > 数据库 > MySQL

MySQL字符集问题

2015-09-06 18:23 465 查看
遇到了这么个问题,数据库表的字符集是utf8,但客户端程序在建立连接时设置了:set names latin1。

查询数据库中的中文字符“尊”的列,返回的是”尚“。

“尊”和“尚”被错误的认为是相等的。

要解释上边的怪现象需要先明确以下几个概念:

MySQL Charset(字符集)

MySQL Collate(校对集)

character_set_client

character_set_connection

character_set_results

需要知道这三个函数

hex()

unhex()

convert()

这几个概念网上的资料很多,在这里就不废话了。

需要特别说明下,character_set_client是客户端传输数据到服务器端使用的字符集。

character_set_connection是服务器端在处理文本时默认使用的字符集。这个character_set_connection因为名字用了connection,比较容易产生误解,我一开始就误解成了客户端和服务器端建立连接使用的字符集。


假设set names utf8,此时character_set_connection的值将被设置为utf8.

执行 select‘尊’;

相当于 select (_utf8 ‘尊’)

(在这里我们使用一个比较特殊的符号“_utf8”,它叫做“引介词”,可参考资料)


单独的一个select并不能说明问题,我们对字符串进行一下比较,执行以下SQL:

select * from test where name=’尊’

假设列test.name的字符集是utf8,此时字符串’尊’的字符集也是utf8,将直接进行比较,不需要进行字符集转换。

此时的SQL相当于:

select * from test where name = (_utf8 ‘尊’)


如果set names gbk后再次运行以上SQL,此时test.name的字符集仍然是utf8,但‘尊’的字符集变成了gbk,等价SQL如下:

select * from test where name = (_gbk ‘尊’)

在进行字符串比较时会进行隐式的字符集转换。

关于字符集比较和隐式转换可参考资料(MySQL Manual - Collation of Expressions



理解了以上几个概念,我们解释下文章开头的怪现象。

由于设置了set names latin1,字符从客户端传到服务器端使用的是latin1字符集

由于数据库表中的字符集是utf8,在进行比较的时候会再次将latin1的字符隐式转换成utf8。

以上比较绕的整个过程可以简化为如下一条SQL:

select convert(_latin1’尊’ using utf8)

(将’尊’先转化为latin1编码,再转换为utf8编码)

前期存到数据库的汉字“尚”也是这样一个转换过程,使用如下SQL检验:

select convert(_latin1’尊’ using utf8) = convert(_latin1’尚’ using utf8);

返回结果为相等。

以上实验只是重现了相等的过程,并没有解释为什么会相等。

这牵扯latin1编码的问题。Latin1是单字节编码,不认识汉字,但将汉字转换成latin1编码的时候将保留原来的编码格式直接存储。如汉字“尊”和“尚”的编码为:

select hex(‘尊’) => E5B08A

select hex(‘尚’) => E5B09A

这两个汉字被编码为3个字节,这三个字节在latin1字符集里对应的字母是:å°Š和å°š

这两个字符串å°Š和å°š在UTF8的默认比较规则下相等。

查看’尊’被转换后成为的十六进制:

select hex(convert(_latin1’尊’ using utf8)) => C3A5C2B0C5A0

查看’尊’被转换后对应的字符:

select unhex(hex(convert(_latin1’尊’ using utf8))) => å°Š

OK,解释完毕。

总结如下:

不能瞎用latin1连接去查询或操作utf8编码的数据库数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: