[mysql基础文档]-21-中文乱码原理与解决方法

引言

本文以MySQL字符集转换储存原理为基础,提供了MySQL中文乱码以及网页中文乱码的解决方法。

文章目录

0×1.MySQL字符集转换过程

先来简单看看MySQL是如何将我们输入的中文储存在数据库中的,了解这一部分内容对理解乱码产生的原因很有帮助,也更容易理解本文后面的内容。

● MySQL系统变量中的字符集

					--使用下面的命令,查看安装MySQL后默认的字符集转换参数
					mysql> show variables like '%char%';
					+--------------------------+----------------------------+
					| Variable_name            | Value                      |
					+--------------------------+----------------------------+
					| character_set_client     | utf8                       |
					| character_set_connection | utf8                       |
					| character_set_database   | latin1                     |
					| character_set_filesystem | binary                     |
					| character_set_results    | utf8                       |
					| character_set_server     | latin1                     |
					| character_set_system     | utf8                       |
					| character_sets_dir       | /usr/share/mysql/charsets/ |
					+--------------------------+----------------------------+
					

参数解析:

character_set_server:默认的内部操作字符集;
character_set_client:客户端来源数据使用的字符集;
character_set_connection:连接层字符集;
character_set_results:查询结果字符集;
character_set_database:当前选中数据库的默认字符集;
character_set_system:系统元数据(字段名等)字符集 ;

● MySQL字符集转换过程遵循以下四步

1)当使用insert into或者其他方法向数据库插入中文数据时,原始数据首先被当做character_set_client值所设定的字符集;

2)将插入的数据转换为character_set_connection值所设定的编码(相当于转换层);

3)character_set_connection转换结束后,将结果储存到"内部操作字符集",其确定方法如下:

■ 如果有,使用每个数据字段的CHARACTER SET设定值;
■ 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
■ 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
■ 若上述值不存在,则使用默认的character_set_server值对应的编码集;

4)如果遇到查询(select),MySQL将以上三步得到的"内部操作字符集"编码结果转换为character_set_results值所对应的编码集,返回给调用者;

这里有必要说明上面第3步中的前三条,下面分别演示"表中某字段单独的字符集修改方法","数据库默认字符集的修改方法"以及"数据表默认字符集的创建方法"

					--1)表中某字段单独的字符集修改方法
					--语法:
					--ALTER TABLE tbl_name CHANGE c_name c_name CHARACTER SET character_name [COLLATE ...];
					--或
					----ALTER TABLE tbl_name MODIFY c_name CHARACTER SET character_name [COLLATE ...];

					--例如,将表t28的uname字段修改成utf8编码,字符校对集为"utf8_general_ci",校对集是可选参数
					mysql> alter table t28 modify uname varchar(200) character set utf8 collate utf8_general_ci;

					--2)数据表默认字符集的创建与修改在本系列文章"[mysql基础文档]-5-数据表创建修改与删除"中有详细说明,这里不再赘述。

					--3)数据库默认字符集的修改方法
					--创建数据库时设定数据库编码集,ubuntu中不携带charset参数,默认使用latin1编码集
					mysql> create database qingsword_com charset=utf8;
					
					--修改已存在的数据库默认字符集
					--语法:
					--ALTER DATABASE db_name DEFAULT CHARACTER SET character_name [COLLATE ...];

					--例如,将qingsword_com这个数据库的默认字符集修改成utf8,字符校对集为"utf8_general_ci"
					mysql> alter database qingsword_com default character set utf8 collate utf8_general_ci;

					--查看数据库编码(看到最后那个utf8了没,那个就是这个数据库的默认编码集)
					mysql> show create database qingsword_com \G
					************* 1. row *************
					       Database: qingsword_com
					Create Database: CREATE DATABASE `qingsword_com` /*!40100 DEFAULT CHARACTER SET utf8 */

					--查看表编码
					mysql> show create table t28 \G

					--查看t28表所有字段编码
					mysql> show full columns from t28\G

					--最重要的一点,要明白第3步中的那些"DEFAULT CHARACTER SET设定值",对应的就是这一部分实例中这三个地方的值。
					

P.s:字符校对集,_ci(表示大小写不敏感),_cs(表示大小写敏感),_bin(表示按编码值比较)结尾。例如:在字符序'utf8_general_ci'下,字符'a'和'A'是等价的,MySQL中都有哪些字符校对集在下面一小段中会介绍到;

● 字符集转换参数值的修改方法

MySQL可用的编码集以及校对集查看方法如下:

					--查看全部编码集
					mysql> show character set;

					--仅查看包含utf8的编码集
					mysql> show character set like '%utf8%';
					

下面是"show variables like '%char%'"命令所显示的列表中,对应值的修改方法:

					--比如,将character_set_client的值,修改成latin1,其他名称修改方法以此类推
					mysql> set character_set_client=latin1;
					

明白了这些必要的基础知识后,来看看下面的MySQL中文乱码实例分析吧。

0×2.MySQL中文乱码实例分析

实例环境为ubuntu系统,使用终端连接mysql,本地终端环境默认字符编码utf8:

					--mysql默认的字符集参数如下
					mysql> show variables like '%char%';
					+--------------------------+----------------------------+
					| Variable_name            | Value                      |
					+--------------------------+----------------------------+
					| character_set_client     | utf8                       |
					| character_set_connection | utf8                       |
					| character_set_database   | utf8                       |
					| character_set_filesystem | binary                     |
					| character_set_results    | utf8                       |
					| character_set_server     | latin1                     |
					| character_set_system     | utf8                       |
					| character_sets_dir       | /usr/share/mysql/charsets/ |
					+--------------------------+----------------------------+

					--首先创建一个表t50,设置表的内部编码为utf8,输入一个中文"码"字,显示毫无问题
					mysql> create table t50(tx text) charset=utf8;
					mysql> insert into t50 values('码');
					mysql> select * from t50;
					+------+
					| tx   |
					+------+
					| 码   |
					+------+

					--下面,将客户端来源数据使用的字符集设置成gbk,再次插入一个中文字符
					mysql> set character_set_client=gbk;
					mysql> insert into t50 values('码');

					--根据本文第一部分字符集的转换步骤分析
					--第1步,ubuntu终端本地字符集默认是utf8,但此时我们告诉数据库,本地终端所使用字符集是gbk,相当于欺骗了数据库
					--第2步,数据库接收到这个utf8编码的字符,将其当做GBK编码,'码'字的utf8编码是三字节的,但gbk编码中文是2字节编码,所以数据库将这3字节数据前端补0,补充成4个字节,并当做两个gbk字符传递,在转换层根据character_set_connection设置的值,转换成了utf8(乱码在这一步产生)
					--第3步,数据表的内部编码为utf8,无需再转换,直接储存
					--第4步,使用select取出的时候,查询结果字符集character_set_results值也是utf8,所以数据库原封不动的将这个utf8编码数据返回给终端,终端也是utf8编码,显示如下
					mysql> select * from t50;
					+------+
					| tx   |
					+------+
					| 码   |
					| 鐮   |
					+------+

					--继续将返回字符集以及连接字符集都改成gbk,下面这条命令相当于一次性将"character_set_client","character_set_connection","character_set_results"都改成gbk编码
					mysql> set names gbk;

					mysql> show variables like '%char%';
					+--------------------------+----------------------------+
					| Variable_name            | Value                      |
					+--------------------------+----------------------------+
					| character_set_client     | gbk                        |
					| character_set_connection | gbk                        |
					| character_set_database   | utf8                       |
					| character_set_filesystem | binary                     |
					| character_set_results    | gbk                        |
					| character_set_server     | latin1                     |
					| character_set_system     | utf8                       |
					| character_sets_dir       | /usr/share/mysql/charsets/ |
					+--------------------------+----------------------------+

					--插入测试
					mysql> insert into t50 values('码');

					--返回结果更加奇怪了,不是说set names可以解决一切吗?其实,看过本文第一部分就会明白,别忘记数据表t50的编码集是utf8;

					--部分数据库在这一步会插入失败,返回一个错误,提示插入的数据过长,为什么会过长呢?因为我们连接mysql的终端使用的是utf8编码,插入一个字符的汉字相当于插入3个字节的数据,而gbk编码只接受一个字符占2个字节的汉字

					--根据字符集转换步骤分析:
					--第1步,ubuntu终端本地字符集是utf8,但此时我们告诉数据库,本地终端所使用字符集是gbk,相当于欺骗了数据库;
					--第2步,被当做gbk的utf8编码被扩充成4个字节编码,传至转换层(character_set_connection),转换层设置了gbk编码,所以原封不动的将数据向内传递(已经出现乱码了)
					--第3步,因为数据表设定了默认utf8编码,所以4字节的gbk编码数据被转成了utf8编码储存
					--第4步,select时,内部的utf8编码字符根据character_set_results的值又被转换成4字节的gbk编码,但本地终端是使用utf8编码呢,终端直接将这个4字节的gbk编码当做utf8编码来处理,此时utf8模板与这个4字节gbk编码完全对不上,所以使用'?'代替,细心的朋友可能发现了,我们输入的是一个字符,这里却出现了两个问号,就是因为字符的字节数被扩充成了4字节。

					mysql> select * from t50;
					+------+
					| tx   |
					+------+
					| ��  |
					| ��  |
					| ��  |
					+------+

					--看到这里,可能有人会觉得,那将t50表的编码改成gbk不就好了吗?实际上就算将t50表的编码改成gbk,本地终端使用的还是utf8编码,除非将本地终端也改成gbk,但被破坏的数据是无法恢复的。
					

P.s:总结,根据上面的实验可以得到一个很重要的结论,"character_set_client"以及"character_set_results"的编码设置,要和我们连接数据库使用的终端编码一致,"character_set_connection"连接层使用的字符集要大于或等于"character_set_client"以及"内部操作字符集"(可以根据本文第一部分字符转换过程分析,如果连接层的字符集小于我们传递给数据库的字符集,比如我们传递一个GBK中文字符给数据库,但是连接层使用了ascii编码,ascii编码根本没有包含中文字符,2字节的gbk编码被转换成2字节的ascii编码,这些中文编码大小已经超出了ascii的范围,所以在连接层转换的时候数据全部丢失,最后会得到一堆问号未知数据),"内部操作字符集",比如数据表的charset所设置的编码集要能够包含我们所需要储存的字符。

0×3.网页乱码解决方法

1)设置数据库"character_set_client"以及"character_set_results"值的编码与连接数据库的页面程序所使用的编码一致;

2)使用任何终端操作数据库时,确保终端所使用的编码与"character_set_client"以及"character_set_results"值的编码一致;

3)页面代码创建表时,使用charset参数定义表字符集,设置的字符集要能包含所有需要储存到表中的字符;

4)"character_set_connection"连接层使用的字符集要大于或等于"character_set_client"以及"内部操作字符集";

5)将以上所有字符集统一成一种字符集,避免内部字符集转换能够提高数据库效率;

P.s:中文环境下Windows命令行终端所使用的编码为GBK,win下phpmyadmin的编码也被设置成了gbk,但是网页的编码却是utf8,win下数据库的"character_set_client"以及"character_set_results"的编码全部被设置成了utf8,根据本文前两部分的分析很容易理解,用win命令行或phpmyadmin对数据库插入数据的时候,在网页上调用这些数据显示乱码的原因。