霜天部落 | 关注LAMP高性能、高并发架构的设计与研究

MySQL 中文乱码问题总结

Mysql自4.1以后,增加了对字符集的支持。笔者之前对Mysql比较了解,刚接触4.1时,感觉Mysql有点多此一举,但后来细想发现,对字符集的支持,虽然对开发者来说,会麻烦一些,但不可否认,是一种进步。对字符集的支持,不仅更加支持多语言,而且,也方便移植。刚开始使用Mysql4.1,你可能感觉有点不适,下面,简单阐述一下笔者对Mysql4.1字符集的理解,再讲述如何PHP如何适应Mysql的这种变化,希望大家看过这文章后,能够有所收获。如果你对计算机基础知识不了解,请直接阅读“结论篇”

一.原理篇

Mysql的字符集里有两个概念,一个是“Character set(字符集)”,另一个是“Collations”。

1. Collations

Collations翻成中文是“校验”,在网页开发的过程中,这个词汇,只在Mysql里使用,主要作用是指导Mysql对字符的比较,比如, ASCII字符集里,Collations规定了a小于b,a等于a,以及a是否等于A之类的。通常,大家基本可以忽略Collations的存在,因为每个字符集都有一个默认的Collations,通常,使用默认的Collations就可以了。

2.字符集

与这对比的是,字符集是个更广的概念,即使是Windows下普通的文本文件,也渗及到字符集的问题。不同的字符集,规定了不同的字符的编码方式。一个 character set (字符集)是一组符号和编码,比如,ASCII字符集,包括的字符有:数字,大小写字母,分号、换行之类的符号,编码方式是用一个7bit表示一个字符(A的编码是65,b的编码是98)。ASCII只规定了英文字母的编码,非英文语言不能用ASCII编码表示,为此,不同的国家,都为自己的语言做了编码,比如,我们国家,就有GB2312编码。但每个国家之间的编码不同,也存在着一些跨平台的问题,为此,一些国际化标准组织,就制定了一些国际通用的编码,最常用的就是UTF8了。ASCII只对英文符号和英文字母做了编码,GB2312对英文符号,英文字母,汉字做了编码,UTF8对世界上所有的语言文字做了编码,所以,GB1212的字符包含了ASCII字符,UTF8包含了GB2312字符。由此可见,UTF8是所含最广字符的字符集,所以,在一些多语言的WEB系统中,一般用UTF8字符集(PHPMyAdmin使用UTF8编码)。

任何文本的存储,都渗及到字符集的概念。包括数据库,也包括普通的文本文件。

主要术语:

字符:汉字,英文字母,标点符号,拉丁文等等。

编码:将字符转换成计算机存储的格式,比如,A用65表示。

字符集:一组字符以及对应的编码方式。

a. Mysql的字符集

Mysql目前支持多字符集,并且,支持在不同的字符集之间转换(便于移植和支持多语言)。

Mysql可以设置服务器级字符集、数据库级字符集、数据表级字符集、表列的字符集,实际上,最终使用字符集的地方是存储字符的列,比如,你设置 table1中col1列是字符类型,col1才用到了字符集,如果table1表的col2列是int类型,col2不使用字符集的概念。

服务器级字符集、数据库级字符集、数据表级字符集都是为列的字符集做默认选项的。

Mysql一定有一个字符集,可以通过启动时加参数指定,也可以编译时指定,也可以在配置文件里指定。Mysql服务器字符集,只是做为数据库级的默认值。创建数据库时,你可以指定字符集,如果没指定,就使用服务器的字符集。同理,创建表时,你可以指定表级的字符集,如果没指定,使用数据库的字符集做为表的字符集。创建列时,你可以指定某列的字符集,如果没指定,就使用表的字符集。

通常情况下,您只需设置服务器级的字符集,其它的数据库级,表级,以及列级的字符集,都继承自服务器级字符集。

由于UTF8是最广的字符集,所以,一般情况下,我们设置Mysql服务器级的字符集为UTF8!

b. 普通文本的字符集问题

任何文本的存储,都存在着字符集的问题,普通文本文件也不例外。

Windows2000+的系统中,打开记事本,“保存为…”对话框,就有一个选项,可以让你选择存储文本的编码方式。

通常情况下,大家都使用Windows2000+的系统,都使用默认的编码,所以,不会碰到字符集的问题。

Windows下,保存文本文件时,可以选择编码方式,但打开文本文件时,都是自动判断编码方式的。网上有一个用Windows2000+的记事本玩移动,联通的笑话,大家可以搜搜,就是因为Windows在打开文本文件时,编码判断错误引起的问题。

因为自动判断编码有时会错误,所以,有的文本文件,规定了如何识别自身所使用的编码。HTML文件就是一个这样的例子。

HTML是文本文件。存储HTML文件的时候,需要使用一个编码,并且,在HTML文件里,也使用HTML语法,指定了该文件所使用的编码(比如)。如果HTML文件没有指定编码,则浏览器自动识别文件的编码。如果HTML指定了编码,则浏览器使用HTML指定的编码。

通常情况下,HTML文件指定的charset和HTML文件自身的编码是一致的,但也有不一致的情况,如果不一致,就会导致网页乱码(此处乱码,只和文本文件有关,和数据库无关。)使用专门的网页编辑工具(比如Dreamwave),会自动根据网页中的charset值来编码文件。

c. php+mysql的字符集问题

PHP最终生成的是文本文件,但他要取数据库里的文本,或将文本存进数据库。

由于Mysql支持多字符集,默认情况下,Mysql不知道PHP发给他的是什么编码的字符,所以,Mysql要求客户端(PHP)告诉他存取的字符集是什么。

PHP通过设置character_set_client,告诉Mysql,PHP存进数据库的是什么编码方式。

PHP通过设置character_set_results,告诉Mysql,PHP需要取什么样编码的数据。

PHP通过设置character_set_connection,告诉Mysql,PHP查询中的文本,使用什么编码。

MYSQL使用设置的编码方式存储文本。

假设Mysql使用setserver来存储文本,PHP的character_set_client是setclient,PHP的 character_set_results是setresult。那么,Mysql将PHP发来的文本,从setclient编码方式,转换成 setserver编码方式,再存入数据库,如果PHP取文本,Mysql将文本从setserver转换成setresult,再发送给PHP。

PHP文件(最终生成的HTML文件)本身有个编码,如果Mysql传过来的编码,与PHP文件自身的编码不同,那么,整个网页,必然乱码。所以,PHP一般将自己的编码方式,告诉Mysql。

要保证不乱码,就必须将三个编码统一:一是网页自身的编码,二是HTML里指定的编码,三是PHP告诉Mysql的编码(包括character_set_client和character_set_results)。

第一和第二个编码,如果使用DW之类的编辑器写的网页,通常是一致的,但用记事本写的网页,有可能不一致。

第三个编码,需要手工通知Mysql。这步可以通过在PHP里使用mysql_query(“set names characterX”)来实现。

d.字符集的转换问题

如果小字集转换成大字符集,不会丢失数据,但大字集,转换成小字集,可能会丢失数据。

比如,UTF8里有的字符,GB2312不一定有,所以,从UTF8转换到GB2312可能会丢失一些字符。

但有种情况例外,先从GB2312转成UTF8,再从UTF8转成GB2312,这种情况是不会丢数据的,因为,刚开始转换的文本,都是GB2312里的字符,所以,整个过程都是GB2312的字符在转换,不会丢失。

正因为UTF8能容纳世界上的所有字符,所以,数据库一般使用UTF8编码。这使得,任何字符都可以存进UTF8编码的数据库。

e. PHPMyAdmin乱码的问题

PHPMyAdmin支持多国语言,这就必定要求HTML页面使用UTF8编码。

HTML页面使用UTF8编码,这就必定要求PHPMyAdmin连接Mysql时,character_set_client和character_set_results使用UTF8编码。

当前情况下,PHP连接Mysql只能是使用set names(或其它几个语句)来通知Mysql的编码方式,如果没有显式的声明编码方式,都将使用latin1编码。一般的程序,都没有显式声明 character_set_client变量,所以,都是将gb2312文本,按latin1编码方式存在数据库,PHPMyAdmin再用utf8格式读取,肯定是乱码的。

如果PHP程序按正确的编码存入数据库,肯定是没有问题的。所以,需要修改的不是PHPMyAdmin.(虽然有时修改PHPMyAdmin可以解决乱码问题,但这不是问题的根本)
二.总结篇

上面的讲得有点乱,总结一下:

1.数据库尽量使用utf8存储(修改/etc/my.cnf,在[mysqld]段加上default-character-set=utf8)

(已有的数据库,先转成UTF8格式)

2.PHP程序在查询数据库之前,执行mysql_query(“set names xxxx”);其中xxxx是你网页的编码(charset=xxxx),如果网页中charset=utf8,则xxxx=utf8,如果网页中 charset=gb2312,则xxxx=gb2312,如果网页中的charset=ipaddr,则xxxx=ipaddr (开个玩笑,没这编码)

几乎所有WEB程序,都有一段连接数据库的公共代码,放在一个文件里,在这文件里,加入mysql_query(“set names”)就可以了。

3.PHPMyAdmin不需要做改动。

4.需要注意的是,为保证网页实际编码(Windows保存对话框里的编码)和他声明的编码(charset=?)是一致的,请用DW之类的工具做网页。

(二)、

Mysql的字符集的独特性体现在已下个方面:
  1. 存在不同级别的默认设置。MySQL4.0以后的对数据库字符集存在4+1个级别的支持。 a) 系统级由/etc/my.cf;windows下为%MySQLHOME\my.ini 系统配置文件定义

    b) character_set_server(服务器):这是设置服务器使用的字符集

    c) character_set_client(客户端) :这是设置客户端发送查询使用的字符集

    d) character_set_connection:这是设置服务器需要将收到的查询串转换成的字符集

    e) character_set_results :这是设置服务器要将结果数据转换到的字符集,转换后才发送给客户端

  2. 特殊的字符集转换流程。client(如servlet)发送一个查询 -> 服务器收到查询,将查询串从character_set_client 转换到character_set_connection,然后执行转换后的查询 -> 服务器将结果数据转换到character_set_results字符集后发送回客户端。

mySQL乱码问题分析解决:

  • 假设在webapp应用中的所有jsp页面都采用gb2312编码,并构建全局过滤器过滤所有请求的编码格式为gb2312。
  • MySQL用 alter database dbName character set gb2312 修改了数据库的字符集编码,以存放中文字符。
  • 那么在正常情况下,查看数据库各级别字符集支持 mysql> SHOW VARIABLES LIKE ‘character_set_%’;mysql> SHOW VARIABLES LIKE ‘collation_%’;

    会发现所有级别默认设定为UTF-8,在CRUD操作过程中这当然会导致编码冲突并产生乱码。

  • 那么我们自然会想到将所有级别的字符集设定为gb2312。set character_set_client = gb2312
    set character_set_server = gb2312
    set character_set_connection = gb2312
    set character_set_results = gb2312

ok 设定完后,调试发现仍然存在乱码? 那么这是为什么?其实问题出在系统级的字符集设定上。我的理解是,当client(这里当然就是webapp)以任何方式与MySQL建立请求,并用SQL进行CRUD操作时,系统会预先由MySQL的预设系统级字符编码(latin)来做一次转换。那么原先按gb2312编码的字符会被按照latin来解码,这时的字符已经为乱码。

  • 所以,必须去修改my.ini文件里的 default-character-set=gb2312 或者在命令行输入mysqld –default-character-set=latin1

不查manual的话,这最后的一条还真是很难想到。