- 字符集与字符编码
字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。字符集是多个字符的集合, 字符集种类较多,每个字符集包含的字符个数 不同,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符 编码,以便计算机能够识别和存储各种文字。
编码: 规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。
编码(encoding)和字符集不同。字符集只是字符的集合,不一定适合作网络传送、处理,有时须经编码(encode)后才能应用。如Unicode可依不同需要以UTF-8、UTF-16、UTF-32等方式编码。
字符编码就是以二进制的数字来对应字符集的字符。因此,对字符进行编码,是信息交流的技术基础。
各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。
注意:Unicode字符集有多种编码方式,如UTF-8、UTF-16等;ASCII只有一种;大多数MBCS(包括GB2312)也只有一种。
- 乱码的背景
由于最近存储过程脚本 source 到数据库后执行,总是碰到数据乱码的问题,浪费了 RD 很大一部分的人力,也给 qa 带来了一些精力的浪费 ; 同时相关产品线也有类似的问题的存在 ; 所以,解决该问题意义重大。
- 数据库编码问题
Mysql 中对于字符集的支持细化到四个层次: 服务器(server),数据库(database),数据表(table)和连接(connection)。
查看系统的字符集和排序方式的设定可以通过下面的两条命令:
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
mysql> SHOW VARIABLES LIKE 'character_set_%';
+--------------------------+---------------------------------------------+
| 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 | gbk |
| character_set_system | utf8 |
| character_sets_dir | /home/work/mysql5051b/share/mysql/charsets/ |
+--------------------------+---------------------------------------------+
8 rows in set (0.00 sec)
而线上的编码方式为
SHOW VARIABLES LIKE 'character_set_%';
character_set_client ,这是用户告诉MySQL查询是用的什么字符集。
character_set_connection ,MySQL接受到用户查询后,按照character_set_client将其转化为character_set_connection设定的字符集。
character_set_results , MySQL将存储的数据转换成character_set_results中设定的字符集发送给用户,是用作显示的字符集。
此三处的字符设定很大程度上会解决乱码问题,那么着三个设定具体有什么作用呢?
character_set_client指定的是Sql语句的编码,如果设置为 binary,mysql就当二进制来处理,character_set_connection指定了mysql 用来运行sql语句的时候使用的编码,也就是说,程序发送给MySQL 的SQL语句,会首先被MySQL从character_set_client指定的编码转换到character_set_connection指定的 编码,如果character_set_clien指定的是binary,则MySQL就会把SQL语句按照 character_set_connection指定的编码解释执行.
当执行SQL语句的过程中,比如向数据库中插入字段的时候,字段也有编码设置,如果字段的编码设置和character_set_connection指 定的不同,则MySQL 会把插入的数据转换成字段设定的编码。SQL语句中的条件判断和SQL插入语句的执行过程类似. 当SQL执行完毕像客户端返回数据的时候,会把数据从字段指定的编码转换为character_set_results指定的编码,如果 character_set_results=NULL 则不做任何转换动作,(注意这里设置为NULL不等于没有设置,没有设置的时候MySQL会继承全局设置), 工作中比较有用的就是利用MySQL进行转码、不同编码的数据库之间共用数据。
- 乱码的原因
由第三步,我们可以清楚看出线上库与开发库的差异,两者的编码方式存在较大的差异,开发机采用的是 gbk ,而线上机器使用的是 utf8 编码,所以在开发机上测试通过的,可能会在线上出现乱码问题。
那么乱码的根本原因是什么那?
首先、在 windowsxp 先开发的存储过程采用 utf8 编码保存,但是上传到开发机(通过 secureCRT ),然后打开文件是会发现存在开始的两个字符是乱码,即使我们在 windows xp 下没有任何字符,其实这个问题困扰了我很长时间,比如, 1 、上传到 linux 下的 sql 脚本,开头的两个字符乱码; 2 、在进行数据操作时,采用 utf8 格式修改了 ud 提供的 csv 文件,但通过 java 代码读出每一行时,发现行的开始存在乱码的现象。经过仔细阅读 unicode 编码及其分类后,才明白为什么会出现上面的两个问题。首先要明白一点, windows 下处理 utf8 文件,会在文件的开头位置添加一个 bom 头,而在 linux 系统下,处理 utf8 文件时,是不需要开头的这个编码的标示符( bom 头)的,所以造成了在 windows 下编辑的 utf8 文件在 linux 下查看会存在乱码的现象。那么这个问题怎么解决那?
- 乱码的解决方案
在 windows XP 下编辑 utf8 文件后,需要将该文件保存为 utf8 no bow 的格式,这样的话就可以屏蔽 windows 和 linux 对于不同 utf8 编码的处理,就不会使上传的 utf8 文件变为乱码。
下面主要描述一下对于该问题的处理过程:
1 、将 sql 脚本保存为 utf8 编码,上传 linux 系统后查看一下文件内容显示如下:
锘緿ROP PROCEDURE IF EXISTS test;
delimiter ;;
CREATE PROCEDURE test(OUT o_ret INT)
BEGIN
insert into test(testname) values(convert('abc娴嬭瘯绗洓鍡峰椃def1243' using utf8));
set o_ret = 1;
END;;
delimiter ;、
执行source /home/work/mysql5051b/test.sql操作时,爆出错误 :
这个原因就是 windows 中的 utf8 格式的处理和 linux 中的 utf8 格式编码的不同导致, window 中的 utf8 文件中开头需要一个 bom 来做标示,而 linux 中处理 utf8 格式文件,不需要 bom 头,所以 linux 就将 sql 脚本的开头几个编码当做内容来解析,于是在 linux 中看到 sql 脚本中看到如上面的两个乱码。
2 、将 sql 脚本保存为 utf8 no bom 编码的文件,上传到 linux 系统后查看一下文件内容显示如下:
[work@bb-un-rd00.bb01.baidu.com mysql5051b]$ more test.sql
DROP PROCEDURE IF EXISTS test;
delimiter ;;
CREATE PROCEDURE test(OUT o_ret INT)
BEGIN
insert into test(testname) values(convert('abc娴嬭瘯绗洓鍡峰椃def1243' using utf8));
set o_ret = 1;
END;;
delimiter ;
执行source /home/work/mysql5051b/test.sql操作,执行成功,call test(@a);select @a;执行成功,然后查看表中的数据,发现表中的数据乱码。
经排查发现如下参数存在问题:
| character_set_client | gbk |
| character_set_connection | gbk
| character_set_database | utf8 |
先了解一下 mysql 的一次会话的过程: 在mysql的一次会话中,服务器收到客户端发来的指令后,大致要执行3个动作:
1 、服务器认为收到的指令是按当前 character_set_client 环境变量所指定的字符集编码的,
2 、将其转换成 character_set_connection 所指定的字符集编码
3 、分析、执行该指令。
注:如果 character_set_connection 所指定的字符集不是中文字符集,则插入后,记录中的的中文仍然无法正常显示。
因为 mysql 服务器是按 character_set_connection 所指定的编码存入数据库的。
因此。 character_set_client 和 character_set_connection 的字符集必须保持一致。
另外, character_set_results 变量所指定的字符集表示服务器向客户端传输数据时所采用的字符集
所以配置文件中的 default-character-set 参数同时表示 character_set_connection 、 character_set_client 、 character_set_results 其实也就是等同于 set names XXX;
2 、将 sql 脚本保存为 gbk 编码的文件,上传到 linux 系统后查看一下文件内容显示如下:
[work@bb-un-rd00.bb01.baidu.com mysql5051b]$ more test3.sql
DROP PROCEDURE IF EXISTS test;
delimiter ;;
CREATE PROCEDURE test(OUT o_ret INT)
BEGIN
insert into test(testname) values(convert('abc测试第四嗷嗷def1243' using utf8));
set o_ret = 1;
END;;
delimiter ;
从内容上看,中文显示是正常的,然后将character_set_client = gbk那么将 sql 脚本 source 后,然后执行存储过程,可以看到插入的中文显示正常;然后将character_set_client = utf8 那么将 sql 脚本 source 后,然后执行存储过程,发现插入的中文编程了乱码。由此可见 sql 脚本的编码必须和character_set_client 的编码保持一致,才能使得存储过程执行后的内容是正常的。
为便于记忆,将乱码问题总结为以下四点:
1、要保证 sql 脚本的编码要与character_set_client,character_set_connection 一致。要保证这一点的话,可以考虑将 sql 脚本的编码直接写在 sql 脚本中:例如:
/*!40101 SET NAMES utf8 */;
DROP PROCEDURE IF EXISTS test;
delimiter ;;
CREATE PROCEDURE test(OUT o_ret INT)
BEGIN
insert into test(testname) values(convert('abc测试第四嗷嗷def1243' using utf8));
set o_ret = 1;
END;;
delimiter ;
2、要保证数据库中存储的数据与数据库编码一致,即数据的编码与character_set_database一致。
从变量中可以看到
| character_set_database | utf8 当前的数据库编码为 utf8 的,而在 sql 脚本中也是将数据转换为 utf8 的编码,见如下:
convert('abc测试第四嗷嗷def1243' using utf8)
3、要保证 SELECT 的返回与程序的编码一致,即 character_set_results 与程序(PHP、Java等)编码一致。
这一点主要是在 java 中配置数据库连接时,已将编码方式设定为了 utf-8 ,参加如下连接配置。
jdbc:mysql://bb-un-rd00.bb01.baidu.com:8500/union4?useUnicode=true&characterEncoding=UTF-8&useCursorFetch=true
4、要保证程序编码与浏览器编码一致,即程序编码与 一致。