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之类的工具做网页。


三. 补充:实战篇(2010-8-21 1:45:33)


需 求:对应的数据和表均已中文UTF8的编码方式进行存储和取出数据,PHP部分也使用UTF8编码形式操作MySQL中的数据。

当前环境:CentOS 5.4 + Apache 2.2.3 + PHP 5.2.10 + MySQL 5.0.77 + phpMyAdmin 3.3.5

编码情况:

系统Console: zh_cn.UTF-8

MySQL: latin 1

[root@localhost ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \\g.
Your MySQL connection id is 170
Server version: 5.0.77 Source distribution

Type 'help;' or '\\h' for help. Type '\\c' to clear the buffer.

mysql> status ;
--------------
mysql  Ver 14.12 Distrib 5.0.77, for redhat-linux-gnu (i686) using readline 5.1

Connection id:          170
Current database:
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.0.77 Source distribution
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    latin1
Conn.  characterset:    latin1
UNIX socket:            /var/lib/mysql/mysql.sock
Uptime:                 2 days 8 hours 27 min 55 sec

Threads: 1  Questions: 1903  Slow queries: 0  Opens: 78  Flush tables: 1  Open tables: 56  Queries per second avg: 0.009
--------------

mysql> show Variables like '%set%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| auto_increment_offset    | 1                          |
| character_set_client     | latin1                     |
| character_set_connection | latin1                     |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | latin1                     |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
9 rows in set (0.00 sec)

my.cnf设置

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Default to using old password format for compatibility with mysql 3.x
# clients (those using the mysqlclient10 compatibility package).
old_passwords=1
# Disabling symbolic-links is recommended to prevent assorted security risks;
# to do so, uncomment this line:
# symbolic-links=0

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

数据库编码设置 utf8_general_ci ,表中中文字段编码采用模式设置utf8_general_ci未改变。

phpMyAdmin链接编码链接校对集utf8_general_ci,数据通过phpMyAdmin添加。

在phpMyAdmin中显示正常。

PHP环境为Zend Framework 默认配置。数据库链接方式采用PDO_MySQL。配置信息如下:
%webroot%/application/configs/application.ini

resources.db.adapter = "PDO_Mysql"
resources.db.params.dbname = "hr"
resources.db.params.host = "localhost"
resources.db.params.username = "root"
resources.db.params.password = "password"

乱码现象

1. PHP通过Zend Framework MVC框架获取的数据设计中文部分显示为????????

2. 使用系统命令行,访问MySQL,查看对应数据库,中文部分同样显示????????


分析问题

1. phpMyAdmin中显示中文正常。说明,单从Zend Framework这个PHP程序的角度就存在设置问题。

2. MySQL命令行中显示乱码,说明client 和 connect 以及server 等相关的字符集不统一,或者,设置的当前字符集不支持UTF8 中文

解决方法

1. MySQL的默认字符集是latin1 总会出现一些问题,因此,将其默认调整成UTF8。

在[mysqld]的section和[client]的section中增加default-character-set = utf8

重启mysqld服务

[root@localhost ~]# vi /etc/my.cnf
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Default to using old password format for compatibility with mysql 3.x
# clients (those using the mysqlclient10 compatibility package).
old_passwords=1
default-character-set = utf8
# Disabling symbolic-links is recommended to prevent assorted security risks;
# to do so, uncomment this line:
# symbolic-links=0

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

[client]
default-character-set = utf8

[root@localhost ~]# /etc/init.d/mysqld restart
停止 MySQL:                                               [确定]
启动 MySQL:                                               [确定]
[root@localhost ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \\g.
Your MySQL connection id is 2
Server version: 5.0.77 Source distribution

Type 'help;' or '\\h' for help. Type '\\c' to clear the buffer.

mysql> status ;
--------------
mysql  Ver 14.12 Distrib 5.0.77, for redhat-linux-gnu (i686) using readline 5.1

Connection id:          2
Current database:
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.0.77 Source distribution
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    utf8
Conn.  characterset:    utf8
UNIX socket:            /var/lib/mysql/mysql.sock
Uptime:                 13 sec

Threads: 1  Questions: 5  Slow queries: 0  Opens: 12  Flush tables: 1  Open tables: 6  Queries per second avg: 0.385
--------------

mysql> use hr ;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from tabCV ;
+----+---------+--------+-------------+--------------------+-----------+--------------------------+--------------------------+---------------+-----------------------+----------------------------+--------------------------------+-----------------+---------------+
| id | user_id | name   | birth_place | identity_card      | education | graduate_school          | major_class              | graduate_time | work_experience_years | network_optimization_years | cv_comments                    | pay_range_start | pay_range_end |
+----+---------+--------+-------------+--------------------+-----------+--------------------------+--------------------------+---------------+-----------------------+----------------------------+--------------------------------+-----------------+---------------+
|  1 |       1 | 李小明   | 北京        | 110111198401011111 |         2 | 北京信息工程学院         | 计算机科学与技术         | 2006-07-01    |                     4 |                          3 | 简历注释,普拉普拉~~~          |            8000 |         10000 |
+----+---------+--------+-------------+--------------------+-----------+--------------------------+--------------------------+---------------+-----------------------+----------------------------+--------------------------------+-----------------+---------------+
1 row in set (0.00 sec)

mysql> show Variables like '%set%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| auto_increment_offset    | 1                          |
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8                       |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
9 rows in set (0.00 sec)

mysql>

2. Zend Framework中增加MySQL链接的Adapter的编码设置。

编辑application.ini,增加adapter的characterset的设置

[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
appnamespace = "Application"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.frontController.params.displayExceptions = 0
resources.db.adapter = "PDO_Mysql"
resources.db.params.dbname = "hr"
resources.db.params.host = "localhost"
resources.db.params.username = "root"
resources.db.params.password = "password"
resources.db.params.charset = "utf8"

[staging : production]

[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.frontController.params.displayExceptions = 1

以上,通过一个实际的问题,结合上述知识,调整了MySQL的编码中的显示乱码问题。今后遇到类似问题,只要仔细分析慢慢处理,虽然缓解比较多。但是一定是可以处理的。

补充

如果只是想在命令行中临时查看一下具体的数据,或者没有办法修改MySQL的配置文件。那么,直接使用命令设置当前会话的编码集,也是一种临时的处理手段。

mysql> set names utf8 ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from dict_status ;
+----+-----------------+------+
| id | name            | desc |
+----+-----------------+------+
|  1 | 正常运行    |      |
|  7 | 机器故障    |      |
|  8 | 机器报废    | NULL |
|  9 | 机器下架    | NULL |
| 12 | 采购中       | NULL |
| 13 | 已到货       | NULL |
| 14 | 流程处理中 |      |
| 15 | 冗余          |      |
| 16 | 机器报修    |      |
| 17 | 已删除       |      |
| 18 | 故障替换    |      |
+----+-----------------+------+
11 rows in set (0.00 sec)

mysql>

其中 SET NAMES 命令等价于

SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;
  • 苏贝

    学习了,原创真不错。。。