Character set顾名思义,就是字符、以及字符对应的编码的集合。例如简体中文字符集gb2312就包括简体中文中的所有规定汉字,以及每个汉字对应的代码。

Collation,是指比较字符的规则的集合。有了比较规则,才能够将一组数据排序——例如按照英文字母顺序排序、汉字按照拼音顺序排序等等。显然,针对同样一组字符集可以有不同的排序标准、规则。例如汉字可以按照拼音排序,也可以按照笔画多少排序。尤其是Unicode的字符集,由于其可以包含不同种类的语言,所以可以按照各种语言的排序方法排序。此外,完全按照字符在字符集里的编码进行比较的方式称为binary比较。

到了这里我们就容易理解了。举例来说,MySQL支持的gb2312字符集中,有gb2312_bin和gb2312_general_ci两种collation。很显然前者是binary比较规则,后者是一般的中文字符比较规则。

每种字符集都有其默认的collation。对于utf8字符集来说,其默认collation是utf8_general_ci。要获得MySQL里面支持的字符集和默认collation列表,可以使用SHOW CHARACTER SET语句:

1 mysql>
SHOW 
CHARACTER SET;
2 +----------+-----------------------------+---------------------+
3 |
Charset  | Description                 | 
Default collation  
|
4 +----------+-----------------------------+---------------------+
5 |
big5     | Big5 Traditional Chinese    | big5_chinese_ci     |
6 |
dec8     | 
DEC West
European           | dec8_swedish_ci     |
7 |
cp850    | DOS West European           | cp850_general_ci    |
8 ...

其次,是MySQL中,在哪些地方需要这些字符集和collation。总体上分,在MySQL的体系中有三处字符集和collation:服务器(数据),连接,客户端。乍一看体系清楚明了,其实并不是这样。下面就一一介绍。

服务器(数据)端

服务器(数据)端的字符集和collation,可以分成四级逐层指定——server, database, table, column。当MySQL存取位于某一列(column)的数据时,如果column的字符集和collation没有指定,就会向上追溯table的;如果table也没有指定字符集和collation,就以database的字符集和collation作为默认值;如果database仍旧没有指定,那么就以服务器的字符集和collation作为默认值。

那么server的字符集和collation的默认值又是从哪里来的呢?答案是,配置文件(my.ini)和mysqld(或者mysqld-nt)的命令行参数中都可以指定。如果不幸的,你根本没有在my.ini或者命令行中指定,那么MySQL就会使用编译MySQL时指定的默认字符集——latin1。

但是,需要注意的是,如果安装MySQL时选择了多语言支持(一般用中文的都会选择吧),安装程序会自动在配置文件中设置default-character-set=utf8。

这样,所有创建的数据库、表,除非明确指出使用其它字符集,都会默认的使用utf作为数据的字符集(同时使用utf8_general_ci作为默认collation,因为它是utf8的默认collation)。

相关系统变量:

1 character_set_server:服务器的字符集
2 collation_server:服务器的collation
3 character_set_database:数据库字符集
4 collation_database:数据库的collation

客户端

对于客户端传送来的literal string(例如INSERT,UPDATE语句当中的值),MySQL需要知道它们是什么编码。同时,MySQL返回给客户端的值(例如SELECT语句的返回值),也可以按照指定的编码返回。

相关系统变量

1 character_set_client:客户端发送过来文字的字符集
2 character_set_results:发送给客户端的结果所使用的字符集

连接

用于连接的字符集和collation,是指MySQL在接受到客户端发送来的文本之后,转换成何种字符集,用什么规则进行比较。需要注意的是,如果是将文本和数据库中某个column的值比较,将优先使用该column的字符集和collation。

相关系统变量

1 character_set_connection:用于连接的字符集
2 collation_connection:用于连接的collation

有了上面的预备知识,我们就开始分析最初的问题:本来是应该作为UTF-8字符保存的数据,为什么到了数据库中就变成了“乱码”?而且这些乱码居然还能毫无问题地被wordpress显示?为什么一旦导入到租用的主机那里就不能正常显示了呢?

首先让我们来看一下,我家里的服务器上,MySQL的系统变量(System Variables)是如何设置的。

注意:因为一些系统变量是根据客户端不同而不同的,所以用mysql命令行登陆所看到的和PHP下看到的并不相同。此外,似乎也不能用PMA查看——似乎在PMA中也已经更改了默认的系统变量。因此,要查看PHP作为客户端时的默认系统变量,可以编写一个类似下面的PHP小程序:

1 mysql_connect(localhost, $user$pass);
2 $query="SHOW
VARIABLES"
;
3 $result=mysql_query($query);

其中$result就包含着所有系统变量。在我家里的服务器上得到了如下结果(以下只列出跟字符集有关的系统变量):

01 character_set_client 
latin1
02 character_set_connection 
latin1
03 character_set_database 
utf8
04 character_set_filesystem 
binary
05 character_set_results 
latin1
06 character_set_server 
utf8
07 character_set_system 
utf8
08 collation_connection 
latin1_swedish_ci
09 collation_database 
utf8_general_ci
10 collation_server 
utf8_general_ci

可见,默认的客户端编码、默认的连接编码是latin1——这也就是说,虽然实际上wordpress传递给MySQL的文本都是用UTF-8编码的,但是由于上述系统变量设置不当,这些UTF-8编码的文本被MySQL当作是latin1编码的,并且由于数据库本身是utf8,因此把这些“latin1文本”又转换成了utf8。这样,一个汉字居然需要6bytes(一个汉字作为UTF-8是3bytes,被当作latin1进行了转换,每个latin1字符转换成2bytes的UTF-8编码)。这就不难理解为什么数据库存储的是“乱码”了。

那么为什么这些“乱码”在wordpress显示时没问题呢?这是因为,character_set_result也是latin1,也就是说MySQL在取出数据交给wordpress时,把这些数据从utf8转换回latin1,然后wordpress将这些latin1又当作了utf8——正好是上面的逆过程。

那么,为什么到另一台服务器上面就无法正常显示了呢?请看看那台租用主机的系统变量设置:

1 character_set_client 
ujis
2 character_set_connection 
ujis
3 character_set_database 
ujis
4 character_set_results 
ujis
5 character_set_server 
ujis
6 character_set_system 
utf8
7 collation_connection 
ujis_japanese_ci
8 collation_database 
ujis_japanese_ci
9 collation_server 
ujis_japanese_ci

可见,其默认的客户端编码是ujis。也就是说,MySQL把utf8数据取出后,将会转换成ujis并传递给wordpress。这经历了latin1 – utf8 – ujis转换的原本是utf8的字符,早已面目全非了……

解决方案

解决方案在很多论坛、网页上已经有提到了,在wordpress的trac也已经有人提出过。

但是在解决问题之前,我却很想知道一个问题的答案,那就是:这到底是MySQL的问题,还是PHP(特别是php_mysql extension)的问题,还是wordpress的问题?甚至是用户配置的问题?我倾向于认为这是一个wordpress的问题。因为无论MySQL还是PHP都不知道wordpress使用了什么字符编码,所以无法更改客户端字符集;而作为一般的wordpress用户,要求他们设置字符编码——可以,但是必须要提供一个用户界面,而不是直接修改源程序。

那么解决方案(或者说只是一个workaround)就是,修改wordpress的\wp-uncludes\wp-db.php。在第40多行的function wpdb中,在$this->select($dbname);之前添加一句:

1 $this->query("SET
NAMES latin1"
);

SET NAMES语句的功能就是,执行了SET NAMES ‘x’相当于下面三条语句的功能。

1 SET
character_set_client = x;
2 SET
character_set_results = x;
3 SET
character_set_connection = x;

这样,在默认客户端字符集是ujis的租用主机上,导入的wordpress文章也能正常显示了。当然,这不是彻底的解决方案——这只是“将错就错”,反正数据库里面存储的已经是被当作latin1而转换成utf8的utf8了,那么就将其转换回所谓的latin1就是了。这样做将使其他程序无法读取wordpress的数据,并且更重要的是,数据库中存储的“utf8数据”无法真正按照utf8应有的排序规则来排序。

那么最彻底的做法,就是在安装wordpress时就添加上面所说的SET NAMES语句,并且设置客户端的字符集为utf8:

1 $this->query("SET
NAMES utf8"
);

但是这样做的话,已经被当作latin1写到数据库里面的文章就会无法正常显示了。要让他们正常显示,必须经过utf8 – latin1的转换。如果数量较多,可以考虑编写一个程序进行转换;数量较少的话……手动转换吧。

BTW,国内高手们汉化的中文版的wordpress中已经添加好这一句了,上面的信息只适用于那些使用英文wordpress的朋友,以及喜欢追根问底的朋友。

PHP+MySQL中字符集问题分析的更多相关文章

  1. mysql中字符集的比较

    Mysql中utf8_general_ci与utf8_unicode_ci有什么区别呢? 在编程语言中,通常用unicode对中文字符做处理,防止出现乱码,那么在MySQL里,为什么大家都使用utf8 ...

  2. Mysql中字符集总结

    有时候,在Mysql数据库中会经常遇到乱码的问题,现在普遍的做法就是全部强行把编码格式都设置成utf8模式,就可以解决这个问题,以前是知其然,不知其所以然,今天我就稍微研究了下Mysql的字符集. 就 ...

  3. MySQL中innodb引擎分析(初始化)

    MySQL的存储引擎是以插件形式工作的,这应该是MySQL的一大特色了吧! 依据<深入理解MySQL>的内容,5.1版本号时存储引擎的插件化都还不是彻底,确切的说是刚加入的特性.为MySQ ...

  4. mysql中explain优化分析

    效率比较  range >index  > all

  5. php mysql apache字符集(二) (转)

    1 MYSQL中的字符集概念  Mysql的字符集里有两个概念,一个是"Character set(字符集)",另一个是"Collations".1.1 Col ...

  6. java面试一日一题:讲下mysql中的索引

    问题:请讲下mysql中的索引 分析:mysql中有很多索引,要对对这些索引有所掌握,还要弄清楚每种索引的本质? 回答要点: 主要从以下几点去考虑 1.索引的本质是什么 2.mysql的索引分类: 3 ...

  7. MySQL字符集 GBK、GB2312、UTF8区别 解决 MYSQL中文乱码问题 收藏 MySQL中涉及的几个字符集

    MySQL中涉及的几个字符集 character-set-server/default-character-set:服务器字符集,默认情况下所采用的.character-set-database:数据 ...

  8. MySQL数据库中字符集的问题

    今天在做Hibernate案例,往mysql中写记录的时候,出现ERROR: Incorrect string value: '\xE5\x8A\xA0\xE5\x86\x85...' for col ...

  9. mysql中slow query log慢日志查询分析

    在mysql中slow query log是一个非常重要的功能,我们可以开启mysql的slow query log功能,这样就可以分析每条sql执行的状态与性能从而进行优化了. 一.慢查询日志 配置 ...

随机推荐

  1. linux ip白名单、防火墙白名单 设置

    http://blog.csdn.net/catoop/article/details/50476099 登录信息在 /var/log/secure linux ip白名单 配置文件:/etc/hos ...

  2. selenium grid应用2-多节点执行用例

    启动远程 node我们目前启动的 Hub 与 node 都是在一台主机.那么要在其它主机启动 node 必须满足以下几个要求: 1)本地 hub 主机与远程 node 主机之间可以相互 ping 通 ...

  3. Xcode及模拟器SDK下载

    http://blog.csdn.net/zhangao0086/article/details/38491271 吐槽下,百度打着无限分享的旗号,却又让分享资源过期,让分享者持续维护 如果你嫌在Ap ...

  4. /proc/sys 子目录的作用

    该子目录的作用是报告各种不同的内核参数,并让您能交互地更改其中的某些.与 /proc 中所有其他文件不同,该目录中的某些文件可以写入,不过这仅针对 root. 其中的目录以及文件的详细列表将占据过多的 ...

  5. Ubuntu每次开机后提示:检测到系统程序出现问题的解决方法

    首先,错误报告存放位置: cd /var/crash/ ls //可以查看错误报告 1 2 sudo rm /var/crash/* //删除该目录下的所有文件 1 但是,这只是删除掉的是错误报告,如 ...

  6. Java Tuple使用实例(转)

    转自链接:http://www.cnblogs.com/davidwang456/p/4514659.html 一.为什么使用元组tuple? 元组和列表list一样,都可能用于数据存储,包含多个数据 ...

  7. SQL:select case when 的用法

    CASE 可能是 SQL 中被误用最多的关键字之一.虽然你可能以前用过这个关键字来创建字段,但是它还具有更多用法.例如,你可以在 WHERE 子句中使用 CASE. 首先让我们看一下 CASE 的语法 ...

  8. python函数库及函数标准库

    一.系统库提供的内部函数 字符函数库: 1)str.islower() :字符串是否全部是小写 2)str.isspace() :字符串是否为空 3)help(str):查询字符串函数库 4)str. ...

  9. 【58沈剑架构系列】主从DB与cache一致性

    本文主要讨论这么几个问题: (1)数据库主从延时为何会导致缓存数据不一致 (2)优化思路与方案 一.需求缘起 上一篇<缓存架构设计细节二三事>中有一个小优化点,在只有主库时,通过“串行化” ...

  10. day1 作业编写登录窗口

    作业一:编写登录接口 (1)输入用户名和密码: (2)认证成功后显示欢迎信息: (3)输错三次后锁定. 思路:我们知道,要想让程序记住之前输入多少次,锁定用户,那么可以使用数据库来保存用户的状态,然而 ...