A MySQL 4.1 Story
下面要写的是一篇非常无聊的东西,充斥了大量各式各样的编码、转换、客户端、服务器端、连接……呃,我自己都不愿意去看它,但想一想,写下来还是有点意义的,原因有四:
- MySQL 4.1 对多语言的支持有了很大变化 (这导致了问题的出现);
- 尽管大部分的地方 (包括个人使用和主机提供商),MySQL 3 仍然占主导地位;但 MySQL 4.1 是 MySQL 官方推荐的数据库,已经有主机提供商开始提供并将会越来越多;
- 许多 PHP 程序以 MySQL 作为默认的数据库管理软件,但它们一般不区分 MySQL 4.1 与 4.1 以下版本的区别,笼统地称“MySQL 3.xx.xx 以上版本”就满足安装需求了;
- 因为 latin1 在许多地方 (下边会详细描述具体是哪些地方) 作为默认的字符集,成功的蒙蔽了许多 PHP 程序的开发者和用户,掩盖了在中文等语言环境下会出现的问题;
简单的说,MySQL 自身的变化和使用 MySQL 的 PHP 程序对此忽略,导致了问题的出现和复杂化,而由于大部分用户使用的是英文,使这种问题不被重视。这里提到的 PHP 程序,主要就 WordPress 而言。
MySQL 4.1 字符集支持的原理
MySQL 4.1 对于字符集的指定可以细化到一台机器上安装的 MySQL,其中的一个数据库,其中的一张表,其中的一栏,应该用什么字符集。但是,传统的 Web 程序在创建数据库和数据表时并没有使用那么复杂的配置,它们用的是默认的配置,那么,默认的配置从何而来呢?
- 编译 MySQL 时,指定了一个默认的字符集,这个字符集是 latin1;
- 安装 MySQL 时,可以在配置文件 (my.ini) 中指定一个默认的的字符集,如果没指定,这个值继承自编译时指定的;
- 启动 mysqld 时,可以在命令行参数中指定一个默认的的字符集,如果没指定,这个值继承自配置文件中的;
- 此时
character_set_server被设定为这个默认的字符集; - 当创建一个新的数据库时,除非明确指定,这个数据库的字符集被缺省设定为
character_set_server; - 当选定了一个数据库时,
character_set_database被设定为这个数据库默认的字符集; - 在这个数据库里创建一张表时,表默认的字符集被设定为
character_set_database,也就是这个数据库默认的字符集; - 当在表内设置一栏时,除非明确指定,否则此栏缺省的字符集就是表默认的字符集;
- 这个字符集就是数据库中实际存储数据采用的字符集,mysqldump 出来的内容就是这个字符集下的;
简单的总结一下,如果什么地方都不修改,那么所有的数据库的所有表的所有栏位的都用 latin1 存储,不过我们如果安装 MySQL,一般都会选择多语言支持,也就是说,安装程序会自动在配置文件中把 default_character_set 设置为 UTF-8,这保证了缺省情况下,所有的数据库的所有表的所有栏位的都用 UTF-8 存储。
当一个 PHP 程序与 MySQL 建立连接后,这个程序发送给 MySQL 的数据采用的是什么字符集?MySQL 无从得知 (它最多只能猜测),所以 MySQL 4.1 要求客户端必须指定这个字符集,也就是 character_set_client,MySQL 的怪异之处在于,得到的这个字符集并不立即转换为存储在数据库中的那个字符集,而是先转换为 character_set_connection 变量指定的一个字符集;这个 connection 层究竟有什么用我不大明白,但转换为 character_set_connection 的这个字符集之后,还要转换为数据库默认的字符集,也就是说要经过两次转换;当这个数据被输出时,又要由数据库默认的字符集转换为 character_set_results 指定的字符集。
一个典型的环境
典型的环境以我自己的电脑上安装的 MySQL 4.1 为例,我自己的电脑上安装着 Apache 2,PHP 5 和 WordPress 1.5.1.3,MySQL 配置文件中指定了 default_character_set 为 utf8。于是问题出现了:
- WordPress 按照默认情况安装,所以所有的表都用 UTF-8 存储数据;
- WordPress 默认采用的浏览字符集是 UTF-8 (Options->Reading 中设置),因此所有 WP 页面的 meta 中会说明 charset 是 utf-8;
- 所以浏览器会以 utf-8 方式显示所有的 WP 页面;这样一来 Write 的所有 Post,和 Comment 都会以 UTF-8 格式从浏览器发送给 Apache,再由 Apache 交给 PHP;
- 所以 WP 从所有的表单中得到的数据都是 utf-8 编码的;WP 不加转换的直接把这些数据发送给 MySQL;
- MySQL 默认设置的
character_set_client和character_set_connection都是 latin1,此时怪异的事情发生了,实际上是 utf-8 格式的数据,被“当作 latin1”转换成……居然还是转换成 latin1,然后再由这个 latin1 转换成 utf-8,这么两次转换,有一部分 utf-8 的字符就丢失了,变成 ??,最后输出的时候character_set_results默认是 latin1,也就输出为奇怪的东西了。
最神奇的还不是这个,如果 WordPress 中设置以 GB2312 格式阅读,那么 WP 发送给 MySQL 的 GB2312 编码的数据,被“当作 latin1”转换后,存进数据库的是一种奇怪的格式 (真的是奇怪的格式,mysqldump 出来就能发现,无论当作 utf-8 还是当作 gb2312 来读都是乱码),但如果这种格式以 latin1 输出出来,居然又能变回 GB2312!
这会导致什么现象呢?WP 如果使用 MySQL 4.1 数据库,把编码改用 GB2312 就正常了,可惜,这种正常只是貌似正常。
如何解决问题
如果你已经不耐烦了 (几乎是肯定的),google 一下,会发现绝大部分的解答是,query 之前先执行一下:SET NAMES 'utf8',没错,这是解决方案,但本文的目的是说明,这为什么是解决方案。
要保证结果正确,必须保证数据表采用的格式是正确的,也就是说,至少能够存放所有的汉字,那么我们只有两种选择,gbk 或者 utf-8,下面讨论 utf-8 的情况。
因为配置文件设置的 default_character_set 是 utf8,数据表默认采用的就是 utf-8 建立的。这也应该是所有采用 MySQL 4.1 的主机提供商应该采用的配置。所以我们要保证的只是客户端与 MySQL 交互之间指定编码的正确。
这只有两种可能,客户端以 gb2312 格式发送数据,或者以 utf-8 格式发送数据。
如果以 gb2312 格式发送:
SET character_set_client='gb2312'
SET character_set_connection='utf8' 或者
SET character_set_connection='gb2312'
都是可以的,都能够保证数据在编码转换中不出现丢失,也就是保证存储入数据库的是正确的内容。
怎么保证取出的是正确的内容呢?考虑到绝大部分客户端 (包括 WP),发送数据的编码也就是它所希望收到数据的编码,所以:
SET character_set_results='gb2312'
可以保证取出给浏览器显示的格式就是 gb2312。
如果是第二种情况,客户端以 utf-8 格式发送 (WP 的默认情况),可以采用下述配置:
SET character_set_client='utf8'
SET character_set_connection='utf8'
SET character_set_results='utf8'
这个配置就等价于 SET NAMES 'utf8'。
WP 应该作什么修改
还是那句话,客户端要发给数据库什么编码的数据,数据库是不可能确切知道的,只能让客户端自己说明白,所以,WP 是必须发送正确的 SET... 给 MySQL 的。怎么发送最合适呢?台湾的 pLog 同仁给出了一些建议:
- 首先,测试服务器是否 >= 4.1,编译时是否加入了 UTF-8 支持;是则继续
- 然后测试数据库以什么格式存储 (
$dbEncoding); SET NAMES $dbEncoding
对于第二点,WP 的情况是不同的,按照上面的典型配置,只要用 WP,肯定数据库是用 UTF-8 存储的,所以要根据用户设置的以 GB2312 还是 UTF-8 浏览来判断 (bloginfo('charset')),但这个值是要连接数据库以后才能得到的,所以效率最高的方式是连接数据库之后,根据这个配置设置一次 SET NAMES,而不必每次查询之前都设置一遍。
我的修改方式是这样的,在 wp_includes/wp-db.php 中增加:
function set_charset($charset)
{
// check mysql version first.
$serverVersion = mysql_get_server_info($this->dbh);
$version = explode('.', $serverVersion);
if ($version[0] < 4) return;
// check if utf8 support was compiled in
$result = mysql_query(”SHOW CHARACTER SET like ‘utf8′”,
$this->dbh);
if (mysql_num_rows($result) < = 0) return;
if ($charset == ‘utf-8′ || $charset == ‘UTF-8′)
$charset = ‘utf8′;
@mysql_query(”SET NAMES ‘$charset’”, $this->dbh);
}
在 wp-settings.php 的 require (ABSPATH . WPINC . '/vars.php'); 后增加:
$wpdb->set_charset(get_bloginfo('charset'));
W. Chen:
长知识了!
2 August 2005, 11:54 amandy:
好文章!
6 August 2005, 6:25 pm五星级
肩磨 | http://to2100.idv.tw , Network LabFreeBSD . » MySQL 4.1 絪絏:
[...] 05/8/13 MySQL 4.1 絪絏 ゅ彻摸: パ硁砰, 琜硁砰 — @ 2005/8/13 Web4C: A MySQL 4.1 Story [ず闽] PS: 稱眖 MySQL 3.x ┪ 4.0.x ど 4.1.x 程弄 [...]
13 August 2005, 7:14 pmJames:
看了你的文章,我終於搞懂了mysql 4.1到底是如何處理資料編碼的問題了。
29 August 2005, 3:25 pm非常感謝你的分享。
Jason Cui:
好文章,收藏进自己的Blog了。另外,怎样查询自己的mysql当前的字符集?
26 November 2005, 11:30 pm纰庣墖 » Blog Archive » [鍏ㄦ枃杞浇]A MySQL 4.1 Story:
[...] 鍘熸枃鍦板潃锛 http://jjgod.3322.org/2005/07/31/a-mysql-41-story/ [...]
28 November 2005, 11:59 amcube316:
转了一下,不介意吧?
http://cube316.net/blog/?p=4
28 November 2005, 12:03 pmtruncatei:
看MySQL的官方手册,里面关于字符集的介绍比较全面,另外,可以通过
创建连接时指定
useUnicode true
characterEncoding UTF-8
characterSetResults UTF-8
这些选项,配合数据库默认的字符集实现支持中文。
因为字符从浏览器发送时为编码后的普通iso-8859-1文本。
29 November 2005, 10:12 am经过PHP转换为gb2312或utf-8后再发送到数据库时如果再转换编码,
带来的性能浪费可想而知。
jjgod:
MySQL 本身就提供了编码转换的功能,不过还是尽量不作转换的好。
30 November 2005, 2:33 pm纰庣墖 » Blog Archive » [鍏ㄦ枃杞浇]A MySQL 4.1 Story:
[...] PHP閰嶇疆鍏ㄦ敾鐣ヤ箣Windows绡 Build040713鏃犳剰涓鎵捣涓浜涙濈华鍥炰埂鍋堕亣椹鐖佃鎹曚簡閬亣灏忓伔[鍏ㄦ枃杞浇]A MySQL 4.1 Story绋閲屽張绯婃秱 [...]
1 December 2005, 11:42 pmautumnsky:
理解的深刻,讲解的透彻,是我在网上找到的最好的这方面文章。谢谢
2 December 2005, 10:41 am若易:
这片文章太好了, 谢谢!!!
13 December 2005, 12:42 pm马达:
非常好的文章,谢谢
8 January 2006, 6:52 pmLife of LeoLo » MySQL鍗囩礆5.0鑸嘦TF-8涔嬬浉闂滆В姹烘柟娉:
[...] A MySQL 4.1 Story [...]
11 February 2006, 9:05 pmCamenix:
相当清楚。也适用于Perl中UTF-8编码。
13 March 2006, 10:13 pm}舱緢鱗x見:
[廔 ] A MySQL 4.1 Story…
是一篇有曑 MySQL 4.1 }鑨嫉奈恼拢蚁肟赐赆岽蟾拍芙鈒z很多人的疑惑。\r\n;:本O喯胱约篬氲模热挥腥藇|栥了 (而且[氲谋任抑赖膼劧) ,就直接拿O哠每及伞
21 April 2006, 5:56 pm\n文章}仓…
ㄚQ駯ㄊ謯:
[廔尲廔尲] A MySQL 4.1 Story…
R[R[在 }舱緢鱗x見尶吹綈篇 [廔 ] A MySQL 4.1 StoryA MySQL 4.1 Story 堘面妔}0姫明了 MySQL 4.1 的故事,跟恑uv的 Patch 方法,
22 April 2006, 1:19 amMySQL 4.1 有UO楲或是趣的人可以看看唷!……
camenix:
好文章,非常有用。看来,现有的数据库改为utf8必须手工编程导出。
9 May 2006, 10:53 amAndy’s Blog » Mysql4.1缂栫爜璇﹁В鍙奧ordPress缂栫爜瀹屽杽:
[...] 杞嚜http://jjgod.3322.org/2005/07/31/a-mysql-41-story/ [...]
13 May 2006, 2:31 pmdomecc:
好文章,收录了。
15 May 2006, 6:21 pm璧板悜浜戝拰灞辩殑褰肩 » Blog Archive » A MySQL 4.1 Story:
[...] <code>杞嚜锛<a href="http://jjgod.3322.org/2005/07/31/a-mysql-41-story/">http://jjgod.3322.org/2005/07/31/a-mysql-41-story/ </a></code> [...]
24 May 2006, 7:12 pmMago!mosca comi valud! » MySQL 4.1 瀛楃闆嗘敮鎸佺殑鍘熺悊:
[...] 鎽樹簬锛 http://jjgod.3322.org/2005/07/31/a-mysql-41-story/ [...]
8 June 2006, 8:30 pm灏忛鍡栧棖鐨勫埉鐫… · MySQL 4.1 瀛楃闆嗘敮鎸佺殑鍘熺悊:
[...] 鎽樹簬锛毬 http://jjgod.3322.org/2005/07/31/a-mysql-41-story/ [...]
22 June 2006, 3:12 am星辉一冷:
非常详细
19 July 2006, 9:57 pm不过现在没耐心看完它。
谢谢先。
我先尝试下以自己的方式能否解决问题。
维拉科查:
好文章,谢了!
21 August 2006, 4:56 pmlavender:
我对数据库不懂看不明白,有什么简单的明了的操作方法吗?
4 September 2006, 6:53 amjjgod:
lavender:
在提交第一次查询前先提交以下语句:
set names ‘utf8′
4 September 2006, 1:28 pm钀借叧榻 » Blog Archive » MySQL绶ㄧ⒓鑸囦簜纰煎晱椤 (for WordPress)銆:
[...] A MySQL 4.1 Story [...]
5 September 2006, 9:02 pmSuperbil.info » 杞夌⒓鐨勬劅鍕:
[...] A MySQL 4.1 Story [...]
10 October 2006, 4:28 amtheinfor » Mysql 鏁版嵁搴撶紪鐮:
[...] Mysql 鏁版嵁搴撶紪鐮 Posted in tech by theinfor on the December 31st, 2006 杞创涓绡囨枃绔狅紝鍏充簬mysql鏁版嵁搴撶紪鐮佺殑闂锛 鏉ユ簮锛歨ttp://blog.jjgod.org/2005/07/31/a-mysql-41-story/#more-51 涓嬮潰瑕佸啓鐨勬槸涓绡囬潪甯告棤鑱婄殑涓滆タ锛屽厖鏂ヤ簡澶ч噺鍚勫紡鍚勬牱鐨勭紪鐮併佽浆鎹€佸鎴风銆佹湇鍔″櫒绔佽繛鎺モ︹﹀憙锛屾垜鑷繁閮戒笉鎰挎剰鍘荤湅瀹冿紝浣嗘兂涓鎯筹紝鍐欎笅鏉ヨ繕鏄湁鐐规剰涔夌殑锛屽師鍥犳湁鍥涳細 [...]
31 December 2006, 8:54 pmwilliam:
不错,介绍的挺详细,不过对新手来说,还是有点深奥,要是把操作写的更详细点就好了。
3 April 2007, 5:20 pm好文章,偷偷的转贴了一下。
blog.bcchinese.net/duckweed
ocean:
请楼主帮忙,我在linux下配置的环境,向你说的那样SET NAMES ‘utf8′ 当时查看是对的,都变了,但是退出MYSQL重新在进去后,又变回去了!这个问题怎么办啊?请多指教!
3 April 2007, 7:23 pmjjgod:
ocean: 你可以在 my.cnf 里设置 default-character-set=utf8
3 April 2007, 9:13 pmvencie:
自己的数据库都乱码 嘿嘿
9DFF.COM
11 April 2007, 9:56 pmadorechen:
写得不错,很好!
1 January 2008, 11:40 am