深入Mysql字符集设置(转)
近段时间被mysql的编码问题困扰很深,偶然看了 Laruence的一篇讲关于mysql字符集的文章,在涉及编码转换的时候更糊涂了,但觉得很不错,转来分享。
原文出处:http://www.laruence.com/2008/01/05/12.html
基本概念
• 字符(Character)是指人类语言中最小的表义符号。例如’A'、’B'等;
• 给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码(Encoding)。例如,我们给字符’A'赋予数值0,给字符’B'赋予数值1,则0就是字符’A'的编码;
• 给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就是字符集(Character Set)。例如,给定字符列表为{‘A’,'B’}时,{‘A’=>0, ‘B’=>1}就是一个字符集;
• 字符序(Collation)是指在同一字符集内字符之间的比较规则;
• 确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;
• 每个字符序唯一对应一种字符集,但一个字符集可以对应多种字符序,其中有一个是默认字符序(Default Collation);
• MySQL中的字符序名称遵从命名惯例:以字符序对应的字符集名称开头;以_ci(表示大小写不敏感)、_cs(表示大小写敏感)或_bin(表示按编码值比较)结尾。例如:在字符序“utf8_general_ci”下,字符“a”和“A”是等价的;
MySQL字符集设置
• 系统变量:
– character_set_server:默认的内部操作字符集
– character_set_client:客户端来源数据使用的字符集
– character_set_connection:连接层字符集
– character_set_results:查询结果字符集
– character_set_database:当前选中数据库的默认字符集
– character_set_system:系统元数据(字段名等)字符集
– 还有以collation_开头的同上面对应的变量,用来描述字符序。
• 用introducer指定文本字符串的字符集:
– 格式为:[_charset] ‘string’ [COLLATE collation]
– 例如:
• SELECT _latin1 ‘string’;
• SELECT _utf8 ‘你好’ COLLATE utf8_general_ci;
– 由introducer修饰的文本字符串在请求过程中不经过多余的转码,直接转换为内部字符集处理。
MySQL中的字符集转换过程
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
• 使用每个数据字段的CHARACTER SET设定值;
• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
• 若上述值不存在,则使用character_set_server设定值。
3. 将操作结果从内部操作字符集转换为character_set_results。
常见问题解析
这部分转自:http://www.mysqlinfo.com/2012/10/17/mysql_charset/
-------------------------------------------------------------------------------------------------------------------------------
场景1:如果插入的数据是有中文并用utf-8编码,连接时候没有set names xx,没有设定字段字符集,但设定了表级字符集为utf8。
当/usr/bin/mysql -uxx -pxx –default-character-set=latin1连接DB后(没有运行set names utf8 )可以发现 character_set_client ,character_set_connection都是latin1,接着系统会检查内部操作字符集,这里会依次检查几个点:
如果字段字符集设置了,那么会以它作为内部操作字符集来比较,
否则检查表级别的字符集设置,否则检查database级别的字符集,否则检查server级别字符集。常见的一般字段都不设置字符集,但表都会指定字符集的。所以比较对象就是 character_set_connection和内部字符集
latin1(character_set_client ) => latin1(character_set_connection) => utf8(内部字符集) 下图诠释了这个问题
简单解释一下:插入的源数据是utf-8编码(你在ssh客户端设定为utf-8后,你手动输入的就是utf-8编码)
‘你’字会对应3个字节用16进制表示(可以用select hex(‘你’);得出utf8编码),因为连接字符集为latin1,latin1是单字节字符集,相当于字节流了。也就意味着一个字节是一个字符,也就是3个latin1字符。接下来就牵涉到latin1转化到utf8,根据规则utf8对于latin1转码时候都是用两个字节表示一个字符,所以3个latin1字符变为3个utf8字符后就需要占用6个字节。所以mysql存储’你’这个字用了6个字符,比正常的多了一倍。当你下次访问的时候
如果character_set_results 为utf8,那么不会涉及到字符集转码,那你字变为6个字节编码自然会显示为乱码。
但是如果character_set_results 为latin1 那么就会涉及utf8转化为latin1 显示会正常。
场景2:源数据utf8中文,内部表字段类型是latin1.因为latin1是单字节字符集,编码范围\u0000~\u00ff.utf8转化为latin1的时候会先转化为unicode编码(根据字节多少按照特定规则可以转),转为0x4F60,这个编码远远超出latin1可编码范围所以会显示为乱码.这时存储在mysql里面的数据已经是乱的了,所以无论以后result-charset设为什么字符集也是乱码,数据无法恢复。这种情况一定要避免。
UTF8 => UTF8 => LATIN1
所以这就存在两个问题,写入数据时候字符集设置是否正确,读取数据的时候设置是否正确,他们是相关联的
下面表格根据常见的可能情况做了个list
插入中文数据类型 | connection_charset | 表字符集 | 插入后存储在mysql的数据 | result_charset_latin1 | result_charset_utf8 |
utf8 | latin1 | latin1 | 可用正常,utf8编码,字节流 | 正常 | 乱码 |
utf8 | utf8 | latin1 | 乱码不可用,数据mess up | 乱码 | 乱码 |
utf8 | latin1 | utf8 | 可用但占用空间多,存储的已经不是正常utf8编码了 | 正常 | 乱码 |
utf8 | utf8 | utf8 | 可用正常,utf8编码 | 乱码 | 正常 |
表字符集 | mysqldump连接时候result_charset字符集 | 导出后的数据可用性 |
latin1 | latin1 | 可用 |
latin1 | utf8 | 乱码不可用 |
utf8 | latin1 | 乱码不可用 |
utf8 | utf8 | 可用 |
在mysql低版本只有latin1字符集,之所以能存储中文是因为latin1是单字节字符集,并且在插入数据的时候没有涉及字符集转化,因为client,connection字符集(可设定set names latin1) 和表字符集都为latin1。所以插入的中文无论是utf8或者GBK字符集存入mysql db的都是按照以前的编码.读取的时候也result字符集也是latin1那么就会按照源来的编码顺序输出到终端。然后终端会根据locale的编码设置(与数据源的中文类型一致)正常回显。所以无论你的数据源是UTF8或者GBK都不会影响显示。
生产环境建议:在配置文件中[mysql] [mysqldump] [mysqld] 里面都设置字符集为utf8,连接的时候也指定utf8,当然也可以用set names utf8(相当于设置character_set_client,character_set_connection,character_set_results 为utf8) ,ssh连接终端也是utf8.当然
utf8会占用存储空间比GBK的大一些,所以也有的网站会考虑采用GBK。
--------------------------------------------------------------------------------------------------------------------------
检测字符集问题的一些手段
• SHOW CHARACTER SET;
• SHOW COLLATION;
• SHOW VARIABLES LIKE ‘character%’;
• SHOW VARIABLES LIKE ‘collation%’;
• SQL函数HEX、LENGTH、CHAR_LENGTH
• SQL函数CHARSET、COLLATION
使用MySQL字符集时的建议
• 建立数据库/表和进行数据库操作时尽量显式指出使用的字符集,而不是依赖于MySQL的默认设置,否则MySQL升级时可能带来很大困扰;
• 数据库和连接字符集都使用latin1时虽然大部分情况下都可以解决乱码问题,但缺点是无法以字符为单位来进行SQL操作,一般情况下将数据库和连接字符集都置为utf8是较好的选择;
• 使用mysql C API时,初始化数据库句柄后马上用mysql_options设定MYSQL_SET_CHARSET_NAME属性为utf8,这样就不用显式地用 SET NAMES语句指定连接字符集,且用mysql_ping重连断开的长连接时也会把连接字符集重置为utf8;
• 对于mysql PHP API,一般页面级的PHP程序总运行时间较短,在连接到数据库以后显式用SET NAMES语句设置一次连接字符集即可;但当使用长连接时,请注意保持连接通畅并在断开重连后用SET NAMES语句显式重置连接字符集。
其他注意事项
• my.cnf中的default_character_set设置只影响mysql命令连接服务器时的连接字符集,不会对使用libmysqlclient库的应用程序产生任何作用!
• 对字段进行的SQL函数操作通常都是以内部操作字符集进行的,不受连接字符集设置的影响。
• SQL语句中的裸字符串会受到连接字符集或introducer设置的影响,对于比较之类的操作可能产生完全不同的结果,需要小心!