MySQL字符集中文乱码解析
作者:张松坡坡坡坡
问题描述
假设有三个表test_gbk
,test_utf8
,test_latin1
,创建的时候字符集分别为gbk
,utf8
,latin1
。表结构为
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
name | varchar(512) | YES | NULL |
"中"字的gbk
十六进制表示为:0xd6 d0
;utf8 16
进制表示为:0xe4 b8 ad
问题1
执行下列语句:
set names 'latin1'; insert into test_latin1 values( '中'); //此处'中'为gbk格式 select name from test_latin1;
结果是乱码,还是正常显示?
问题2
执行下列语句:
set names 'gbk'; insert into test_latin1 values( '中'); //此处'中'为gbk格式 select name from test_latin1;
结果是乱码,还是正常显示?
问题3
执行下列语句:
set names 'latin1'; insert into test_utf8 values( '中'); //此处'中'为gbk格式 select name from test_utf8;
结果是乱码,还是正常显示?
原理篇
字符集介绍
为了解释上述问题,首先要了解字符集为何物。字符集也叫字符编码,就是将字符集合一一映射成一个数。以下简单介绍一下几种字符集:
基础ASCII
编码:
0x00-0x7F
表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。
latin1
编码:
单字节编码,编码范围是0x00-0xFF
,0x00-0x7F
,和ASCII
保持一致,0x80-0x9F
之间是控制字符,0xA0-0xFF
之间是文字符号。
gbk
编码:
使用一字节和双字节编码,0x00–0x7F
范围内是一位,和ASCII保持一致。双字节的第一字节范围是0x81-0xFE
(不含0x80
和0xFF
)。
utf8
编码:
使用一至四字节编码,0x00–0x7F
范围内是一位,和ASCII保持一致。其它字符用二至四个字节变长表示。
字符集编码转换举例:
0xB1(latin-1)
->'±'
-> 0xC2 B1 (utf8)
两个重要的点
0x00-0x7F
区间,上述字符集是一致的,也就是说英文字符无需转码。不同编码,字符集合不完全一样,存在某字符集的字符无法映射到另外一个字符集。
比如gbk
编码中的中文字符,转成latin-1
编码时,就找不到对应的二进制编码。MySQL做字符集转换的时候,gbk
中文字符->latin-1,很多就转成'?'号(0x3f
),这种大集合转成小集合,基本是不可逆的。
MySQL执行过程
对一个MySQL的执行过程,字符集转换,一般涉及到一下三个步骤:
收到请求,将请求数据从
character_set_client
->character_set_connection
。内部操作,将数据从
character_set_connection
-> 表创建的字符集。结果输出,将数据从表创建的字符集 ->
character_set_results
。
当执行set names "charset"
; 相当于把character_set_client
, character_set_connection
,character_set_results
统一设置为"charset"
。
终端显示字符集
此外如果你用securecrt终端来显示的话,如果不想乱码的话,appearance
->character encoding
也需要设置成正确的字符集。
问题详解
问题1
执行下列语句:
set names 'latin1'; insert into test_latin1 values( '中'); //此处'中'为gbk格式 select name from test_latin1;
结果是乱码,还是正常显示?
答:结果是正常显示。
执行流程如下:
set names 'latin1'
;相当于把character_set_client
,character_set_connection
,character_set_results
统一设置为'latin1'
。Character_set_client
告诉MySQL Server
,传入的是一个latin1编码的,也就是单字节流,'中'这个输入,其实当作了0xD6 D0传入。因为
character_set_client
->character_set_connection
->table charset
->character_set_results
为latin1
->latin1
->latin1
->latin1
, 编码完全一致,数据没有做任何转换,所以输入是0xD6 D0,最后的输出也还原为0xD6 D0。如果你的securecrt的显示字符集设置为
gbk
,那么最后的输出0xD6 D0
就会显示成'中'。
问题2
执行下列语句:
set names 'gbk'; insert into test_latin1 values( '中'); //此处'中'为gbk格式 select name from test_latin1;
结果是乱码,还是正常显示?
答:结果是乱码。
执行流程如下:
set names 'gbk'
;相当于把character_set_client
,character_set_connection
,character_set_results
统一设置为'gbk'
。Character_set_client
告诉MySQL Server
,传入的是一个gbk编码的,'中'这个输入,当作了0xD6 D0
传入。因为
character_set_client
->character_set_connection
->table charset
->character_set_results
为gbk
->gbk
->latin1
->gbk
, 其中gbk
->latin1
的时候,因为'中'这个字符在latin1字符集里找不到,就会转换成'?'号(0x3F
),然后latin1
->gbk
,'?'号在gbk
字符集里面也是0x3F
,最后输出就是0x3F
,即'?'号。
问题3
执行下列语句:
set names 'latin1'; insert into test_utf8 values( '中'); //此处'中'为gbk格式 select name from test_utf8;
结果是乱码,还是正常显示?
答:正常显示。
执行流程如下:
set names 'latin1'
;相当于把character_set_client
,character_set_connection
,character_set_results
统一设置为'latin1'
。Character_set_client
告诉MySQL Server
,传入的是一个latin1
编码的,'中'这个输入,当作了0xD6 D0
传入。因为
character_set_client
->character_set_connection
->table charset
->character_set_results
为latin1
->latin1
->utf8
->latin1
, 其中latin1
->utf8
的时候,输入'中' (0xD6 D0)会当作两个字符进行utf8转换,转换为0xC3 96 C3 90
,然后utf8
->latin1
的时候,会把0xC3 96
转换成0xD6
,0xC3 90
转成0x D0
,最后输出0xD6 D0
。负负得正,之所以数据没有失真的原因是因为小集合往大集合转,再转回来,操作可逆。如果你的securecrt的显示字符集设置为gbk,那么最后的输出
0xD6 D0
就会显示成'中'。
终极解决方案
从上面的问题执行流程来看,有没有终极解决方案呢?其实很简单,表创建的字符集和set names
都设置成同一个字符集,就基本可以满足输入数据不会在转换过程中失真,也就是说输入是什么,输出就是什么。建议有中文的都设置成utf8字符集,一劳永逸。
以上就是MySQL字符集中文乱码解析的详细内容,更多关于MySQL字符集中文乱码的资料请关注脚本之家其它相关文章!