字符集和比较规则简介
字符集简介
我们知道在计算机中只能以二进制的方式对数据进行存储,那么他们之间是怎样对应并进行转换的?我们需要了解两个概念:
- 字符范围:我们可以将哪些字符转换成二进制数据,也就是规定好字符范围。
- 转换过程:将一个字符转换成一个二进制数据的过程也叫做编码 ,将一个二进制数据转换成一个字符的过程叫做解码 。
我们抽象出一个字符集的概念来描述某个字符范围的编码规则。比方说我们来自定义一个名称为 margu的字符集,它包含的字符范围和编码规则如下:包含字符 ‘a’ 、 ‘b’ 、 ‘A’ 、 ‘B’ 。
编码规则如下:
采用1个字节编码一个字符的形式,字符和字节的映射关系如下:
‘a’ -> 00000001 (十六进制:0x01)
‘b’ -> 00000010 (十六进制:0x02)
‘A’ -> 00000011 (十六进制:0x03)
‘B’ -> 00000100 (十六进制:0x04)
如果我们规定使用margu这个字符集,我们就可以用二进制形式表示一些字符串了,下边是一些字符串用 margu 字符集编码后的二进制表示:
‘Ba’ -> 0000010000000001 (十六进制:0x0401)
‘bAB’ -> 000000100000001100000100 (十六进制:0x020304)
‘CD’ -> 无法表示,字符集‘margu‘不包含字符’C’和’D’
所以,如果你想表示某个字符,一定要你选用的字符集支持。
比较规则简介
在我们确定了 margu字符集表示字符的范围以及编码规则后,怎么比较两个字符的大小呢?比较简单的就是直接比较这两个字符对应的二进制编码的大小,比方说字符 ‘a’ 的编码为 0x01 ,字符 ‘b’ 的编码为 0x02 ,所以 ‘a’ 小于 ‘b’ ,这种简单的比较规则也可以被称为二进制比较规则,英文名为 binary collation 。
二进制比较规则是简单,但有时候并不符合现实需求,比如在很多场合对于英文字符我们都是不区分大小写的,也就是说 ‘a’ 和 ‘A’ 是相等的,在这种场合下二进制比较规则就不再适用了,这时候我们可以这样指定比较规则:
1 . 将两个大小写不同的字符全都转为大写或者小写。
2 . 再比较这两个字符对应的二进制数据。
这是一种稍微复杂一点的比较规则,但是实际生活中的字符不止英文字符一种,比如我们的汉字就有上万个,对于某一种字符集来说,比较两个字符大小的规则可以制定出很多种,也就是说同一种字符集可以有多种比较规则,后面会介绍目前常用的一些字符集以及它们的一些比较规则。
一些重要的字符集
目前字符集的种类有很多,它们表示的字符范围和用到的编码规则可能都不一样。下面是一些常用的字符集:
ASCII 字符集
共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共128个字符,所以可以使用1个字节(8个bit位)来进行编码,我们看一些字符的编码方式:
‘A’ -> 01000001(十六进制:0x41,十进制:65)
‘H’ -> 01001000(十六进制:0x48,十进制:72)
ISO 8859-1 字符集
共收录256个字符,是在 ASCII 字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母),也可以使用1个字节来进行编码。这个字符集也有一个别名 latin1 。
GB2312 字符集
收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。同时这种字符集又兼容 ASCII 字符集,所以在编码方式上显得有些奇怪:
- 如果该字符在 ASCII 字符集中,则采用1字节编码。
- 否则采用2字节编码。
这种表示一个字符需要的字节数可能不同的编码方式称为变长编码方式 。比方说字符串 ‘爱u’ ,其中 ‘爱’ 需要用2个字节进行编码,编码后的十六进制表示为 0xCED2 , ‘u’ 需要用1个字节进行编码,编码后的十六进制表示为 0x75 ,所以拼合起来就是 0xCED275 。
注意:如何区分某个字节代表一个单独的字符还是代表某个字符的一部分呢?前面说过ASCII
字符集只收录128个字符,使用0~127就可以表示全部字符,所以如果某个字节是在0~127之内的,就意味着一个字节代表一个单独的字符,否则就是两个字节代表一个单独的字符。
GBK 字符集
GBK 字符集只是在收录字符范围上对 GB2312 字符集作了扩充,编码方式上兼容 GB2312 。
utf8
字符集收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容 ASCII 字符集,采用变长编码方式,编码一个字符需要使用1~4个字节,比如:
‘A’ -> 01000001(十六进制:0x41)
‘啊’ -> 111001011001010110001010(十六进制:0xE5958A)
注意:其实准确的说,utf8只是Unicode字符集的一种编码方案,Unicode字符集可以采用utf8、utf16、utf32这几种编码方案,utf8使用1~4个字节编码一个字符,utf16使用2个或4个字节编码一个字符,utf32使用4个字节编码一个字符。
MySQL中并不区分字符集和编码方案的概念,所以后边唠叨的时候把utf8、utf16、utf32都当作
一种字符集对待。
对于同一个字符,不同字符集也可能有不同的编码方式。比如对于汉字 ‘我’ 来说, ASCII 字符集中没有收录这个字符, utf8 和 gb2312 字符集对汉字 我 的编码方式如下:
utf8编码:111001101000100010010001 (3个字节,十六进制表示是:0xE68891)
gb2312编码:1100111011010010 (2个字节,十六进制表示是:0xCED2)
MySQL中支持的字符集和排序规则
MySQL中的utf8和utf8mb4
上边说过utf8 字符集表示一个字符需要使用1~4个字节,但是我们常用的一些字符使用1~3个字节就可以表示了。而在Mysql中字符集表示一个字符所用最大字节长度在某些方面会影响系统的存储和性能,所以Mysql衍生出utf8的两个子字符集概念:
utf8mb3 :精简版的utf8 字符集,只使用1~3个字节表示字符。
utf8mb4 :标准版的utf8 字符集,使用1~4个字节表示字符。
重点注意:在 MySQL 中 utf8 是 utf8mb3 的别名,所以之后在 MySQL 中提到 utf8 就意味着使用1~3个字节来表示一个字符,如果要想使用4字节编码一个字符的情况,比如存储一些emoji表情,那么需要完整指定使用utf8mb4 字符集。
字符集的查看
MySQL 支持好多好多种字符集,查看当前 MySQL 中支持的字符集可以用下边这个语句:
SHOW (CHARACTER SET|CHARSET) [LIKE 匹配的模式];
其中 CHARACTER SET 和 CHARSET 是同义词,用任意一个都可以。
mysql> show character set;
+----------+---------------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+---------------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| dec8 | DEC West European | dec8_swedish_ci | 1 |
| cp850 | DOS West European | cp850_general_ci | 1 |
| hp8 | HP West European | hp8_english_ci | 1 |
| koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
......
41 rows in set (0.00 sec)
从返回的结果中可以看到,目前使用的mysql(5.7版本)一共支持41种字符集,其中的 Default collation 列表示这种字符集中一种默认的比较规则,ci结尾的表示都是忽略大小写 。最后一列的Maxlen ,它代表该种字符集表示一个字符最多需要几个字节。大家需要对常用的有印象,如下:
字符集名称 | 描述 | 默认比较规则 | 最长字节数 |
---|---|---|---|
ascii | US ASCII | ascii_general_ci | 1 |
latin1 | cp1252 West European | latin1_swedish_ci | 1 |
gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 |
gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
比较规则查看
查看 MySQL 中支持的比较规则的命令如下:
SHOW COLLATION [LIKE 匹配的模式];
一种字符集可能对应有多种比较规则, MySQL 支持的字符集就已经非常多了,所以支持的比较规则更多,我们先只查看一下 utf8 字符集下的比较规则:
mysql> show collation like 'utf8\_%';
+--------------------------+---------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+--------------------------+---------+-----+---------+----------+---------+
| utf8_general_ci | utf8 | 33 | Yes | Yes | 1 |
| utf8_bin | utf8 | 83 | | Yes | 1 |
| utf8_unicode_ci | utf8 | 192 | | Yes | 8 |
......
| utf8_general_mysql500_ci | utf8 | 223 | | Yes | 1 |
+--------------------------+---------+-----+---------+----------+---------+
27 rows in set (0.00 sec)
从返回的结果可以看到关于utf8的比较规则都有27种,这些比较规则的命名有一定的规律,具体规律如下:
- 比较规则名称以与其关联的字符集的名称开头。如上图的查询结果的比较规则名称都是以 utf8 开头的。
- 后边紧跟着该比较规则主要作用于哪种语言,比如 utf8_spanish_ci 表示以西班牙语的规则比较,utf8_roman_ci 是以罗马语的规则比较, utf8_general_ci 是一种通用的比较规则。
名称后缀意味着该比较规则是否区分语言中的重音、大小等,具体可以用的值如下:- _ai:accent insensitive ,不区分重音(insentive:不敏感的)
- _as:accent sensitive ,区分重音
- _ci:case insensitive ,不区分大小写
- _cs:case sensitive,区分大小写
- _bin:binary ,以二进制方式进行比较
比如 utf8_general_ci 这个比较规则是以 ci 结尾的,说明不区分大小写。
每种字符集对应若干种比较规则,每种字符集都有一种默认的比较规则, SHOW COLLATION 的返回结果中的Default 列的值为 YES 的就是该字符集的默认比较规则,比方说 utf8 字符集默认的比较规则就是utf8_general_ci 。
字符集和比较规则的应用
各级别的字符集和比较规则
MySQL 有4个级别的字符集和比较规则,分别是:
- 服务器级别
- 数据库级别
- 表级别
- 列级别
接下来看看怎么查看和设置这几个级别的字符集和比较规则。
服务器级别
MySQL 提供了两个系统变量来表示服务器级别的字符集和比较规则:
系统变量 | 说明 |
---|---|
character_set_server | 服务器级别的字符集 |
collation_server | 服务器级别的比较规则 |
我们看一下这两个系统变量的值,记不住全称的可以用%进行匹配。
mysql> show variables like '%_server';
+----------------------+-----------------+
| Variable_name | Value |
+----------------------+-----------------+
| character_set_server | utf8 |
| collation_server | utf8_general_ci |
+----------------------+-----------------+
2 rows in set (0.01 sec)
可以看到在我的计算机中服务器级别默认的字符集是 utf8 ,默认的比较规则是utf8_general_ci 。
我们可以在启动服务器程序时通过启动选项或者在服务器程序运行过程中使用 SET 语句修改这两个变量的值。比如我们可以在配置文件中这样配置:
[server]
character_set_server=gbk
collation_server=gbk_chinese_ci
当服务器启动的时候读取这个配置文件后这两个系统变量的值便修改了。
数据库级别
我们在创建和修改数据库的时候可以指定该数据库的字符集和比较规则,具体语法如下:
CREATE DATABASE 数据库名
[[DEFAULT] CHARACTER SET 字符集名称]
[[DEFAULT] COLLATE 比较规则名称];
ALTER DATABASE 数据库名
[[DEFAULT] CHARACTER SET 字符集名称]
[[DEFAULT] COLLATE 比较规则名称];
其中的 DEFAULT 可以省略,并不影响语句的语义。比方说我们新创建一个名叫 charset_demo_db 的数据库,在创
建的时候指定它使用的字符集为 gb2312 ,比较规则为 gb2312_chinese_ci :
mysql> create database charset_demo_db character set gb2312 collate gb2312_chinese_ci;
Query OK, 1 row affected (0.00 sec)
mysql>
如果想查看当前数据库使用的字符集和比较规则,可以查看下面两个系统变量的值(前提是使用 USE 语句选择要查看的数据库,如果没有指定要查看的数据库,则变量与相应的服务器级系统变量具有相同的值):
#未指定数据库,值与服务器级别的系统变量一致
mysql> show variables like '%_database';
+------------------------+-----------------+
| Variable_name | Value |
+------------------------+-----------------+
| character_set_database | utf8 |
| collation_database | utf8_general_ci |
| skip_show_database | OFF |
+------------------------+-----------------+
3 rows in set (0.00 sec)
mysql> use charset_demo_db;
Database changed
mysql> show variables like '%_database';
+------------------------+-------------------+
| Variable_name | Value |
+------------------------+-------------------+
| character_set_database | gb2312 |
| collation_database | gb2312_chinese_ci |
| skip_show_database | OFF |
+------------------------+-------------------+
3 rows in set (0.01 sec)
可以看到这个 charset_demo_db 数据库的字符集和比较规则就是我们在创建语句中指定的。需要注意的是:数据库中存在的两个变量(character_set_database、collation_database)都是只读的,没法通过修改这两个变量的值来修改数据的字符集和比较规则,想要修改数据库的字符集和比较规则,需要在创建或者修改数据库的时手动指定character set和collation变量的值,如果不指定的话,则默认使用服务器级别的字符集和比较规则。
表级别
我们也可以在创建和修改表的时候指定表的字符集和比较规则,语法如下:
CREATE TABLE 表名 (列的信息)
[[DEFAULT] CHARACTER SET 字符集名称]
[COLLATE 比较规则名称]]
ALTER TABLE 表名
[[DEFAULT] CHARACTER SET 字符集名称]
[COLLATE 比较规则名称]
比方说我们在刚刚创建的 charset_demo_db 数据库中创建一个名为 test 的表,并指定这个表的字符集和比较规则:
mysql> create table test(name varchar(10) ) CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 0 rows affected (0.08 sec)
mysql>
如果创建和修改表的语句中没有指明字符集和比较规则,将使用该表所在数据库的字符集和比较规则作为该表的字符集和比较规则。假设我们的创建表 test 的语句是这么写的:
CREATE TABLE test(name varchar(10) ) ;
因为表test的建表语句中并没有明确指定字符集和比较规则,则表test 的字符集和比较规则将继承所在数据库charset_demo_db 的字符集和比较规则,也就是 gb2312 和gb2312_chinese_ci 。
列级别
需要注意的是,对于存储字符串的列,同一个表中的不同的列也可以有不同的字符集和比较规则。我们在创建和修改列定义的时候可以指定该列的字符集和比较规则,语法如下:
CREATE TABLE 表名(
列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称],
其他列…
);
ALTER TABLE 表名 MODIFY 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称];
比如我们修改一下表 test 中列 name的字符集和比较规则可以这么写:
mysql> alter table test modify name varchar(10) charset gbk collate gbk_chinese_ci;
Query OK, 0 rows affected (0.21 sec)
Records: 0 Duplicates: 0 Warnings: 0
对于某个列来说,如果在创建和修改的语句中没有指明字符集和比较规则,将使用该列所在表的字符集和比较规则作为该列的字符集和比较规则。比方说表 test的字符集是 utf8 ,比较规则是 utf8_general_ci ,修改列 name的
语句是这么写的:
ALTER TABLE test MODIFY name VARCHAR(10);
那列name的字符集和编码将使用表test的字符集和比较规则,也就是utf8和utf8_general_ci 。
注意:在转换列的字符集时需要注意,如果转换前列中存储的数据不能用转换后的字符集进行表示会发生错误。比方说原先列使用的字符集是utf8,列中存储了一些汉字,而现在把列的字符集转换为ascii的话就会出错,因为ascii字符集并不能表示汉字字符。
仅修改字符集或仅修改比较规则
由于字符集和比较规则是互相有联系的,如果我们只修改了字符集,比较规则也会跟着变化,如果只修改了比较规则,字符集也会跟着变化,具体规则如下:
- 只修改字符集,则比较规则将变为修改后的字符集默认的比较规则。
- 只修改比较规则,则字符集将变为修改后的比较规则对应的字符集。
不论哪个级别的字符集和比较规则,这两条规则都适用,下面以服务器级别的字符集和比较规则为例测试一下:
- 只修改字符集,则比较规则将变为修改后的字符集默认的比较规则。
mysql> set character_set_server = gb2312;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%_server';
+----------------------+-------------------+
| Variable_name | Value |
+----------------------+-------------------+
| character_set_server | gb2312 |
| collation_server | gb2312_chinese_ci |
+----------------------+-------------------+
2 rows in set (0.00 sec)
- 只修改比较规则,则字符集将变为修改后的比较规则对应的字符集。
mysql> set collation_server = utf8_general_ci;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%_server';
+----------------------+-----------------+
| Variable_name | Value |
+----------------------+-----------------+
| character_set_server | utf8 |
| collation_server | utf8_general_ci |
+----------------------+-----------------+
2 rows in set (0.00 sec)
各级别字符集和比较规则小结
下面总结以上4个级别字符集和比较规则之间的联系:
- 如果创建或修改列时没有显式的指定字符集和比较规则,则该列默认用表的字符集和比较规则
- 如果创建或修改表时没有显式的指定字符集和比较规则,则该表默认用数据库的字符集和比较规则
- 如果创建或修改数据库时没有显式的指定字符集和比较规则,则该数据库默认用服务器的字符集和比较规则
知道了这些规则之后,对于给定的表,我们应该知道它的各个列的字符集和比较规则是什么,从而根据这个列的类型来确定存储数据时每个列的实际数据占用的存储空间大小了。具体使用的字符集类型可以通过show create table test;
进行查看,注意表名需要修改成自己要查询的表。
客户端和服务器通信中的字符集
编码和解码使用的字符集不一致的后果
说到底,字符串在计算机上的体现就是一个字节串,如果你使用不同字符集去解码这个字节串,最后得到的结果可能千奇百怪。
我们知道字符 ‘我’ 在 utf8 字符集编码下的字节串长这样: 0xE68891 ,如果一个程序把这个字节串发送到另一个程序里,另一个程序用不同的字符集去解码这个字节串,假设使用的是 gbk 字符集来解释这串字节,解码过程就是这样的:
- 首先看第一个字节 0xE6 ,它的值大于 0x7F (十进制:127),说明是两字节编码,继续读一字节后是 0xE688 ,然后从 gbk 编码表中查找字节为 0xE688 对应的字符,发现是字符 ‘鎴’
- 继续读一个字节 0x91 ,它的值也大于 0x7F ,再往后读一个字节发现木有了,所以这是半个字符。
- 所以 0xE68891 被 gbk 字符集解释成一个字符 ‘鎴’ 和半个字符。
假设用 latin1 字符集去解释这串字节,解码过程如下:
- 先读第一个字节 0xE6 ,它对应的 latin1 字符为 æ 。
- 再读第二个字节 0x88 ,它对应的 latin1 字符为 ˆ 。
- 再读第二个字节 0x91 ,它对应的 latin1 字符为 ‘ 。
- 所以整串字节 0xE68891 被 latin1 字符集解释后的字符串就是 ‘我’
可见,如果对于同一个字符串编码和解码使用的字符集不一样,会产生意想不到的结果,作为使用者的我们看上去就像是产生了乱码一样。
字符集转换的概念
如果接收 0xE68891 这个字节串的程序按照 utf8 字符集进行解码,然后又把它按照 gbk 字符集进行编码,最后编码后的字节串就是 0xCED2 ,我们把这个过程称为 字符集的转换 ,也就是字符串 ‘我’ 从 utf8 字符集转换为gbk 字符集。
Mysql中字符集的转换
我们知道从客户端发往服务器的请求本质上就是一个字符串,服务器向客户端返回的结果本质上也是一个字符串,而字符串其实是使用某种字符集编码的二进制数据。这个字符串可不是使用一种字符集的编码方式一条道走到黑的,从发送请求到返回结果这个过程中伴随着多次字符集的转换,在这个过程中会用到3个系统变量,我们先把它们写出来看一下:
系统变量 | 描述 |
---|---|
character_set_client | 服务器解码请求时使用的字符集 |
character_set_connection | 服务器处理请求时会把请求字符串从 character_set_client 转为 character_set_connection |
character_set_results | 服务器向客户端返回数据时使用的字符集 |
这几个系统变量在我的计算机上的默认值如下(不同操作系统的默认值可能不同),注意只关注上面给的几个变量,其他的在测试过程中应该手动改过。
mysql> show variables like 'character_set_%';
+--------------------------+----------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | gb2312 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/charsets/ |
+--------------------------+----------------------------------+
8 rows in set (0.01 sec)
可以看到这三个系统变量的值都是 utf8 ,为了体现出字符集在请求处理过程中的变化,我们这里特意修改 一个系统变量的值:
mysql> set character_set_connection = gbk;
Query OK, 0 rows affected (0.00 sec)
所以现在系统变量 character_set_client 和 character_set_results 的值还是 utf8 ,而 character_set_connection 的值为 gbk 。现在假设我们客户端发送的请求是下边这个字符串:
SELECT * FROM t WHERE s = ‘我’;
为了方便大家理解这个过程,我们只分析字符 ‘我’ 在这个过程中字符集的转换。 现在看一下在请求从发送到结果返回过程中字符集的变化:
- 客户端发送请求所使用的字符集
一般情况下客户端所使用的字符集和当前操作系统一致,不同操作系统使用的字符集可能不一样,如下:
- Linux系统使用的是 utf8
- Windows 使用的是 gbk
我在windows服务器上使用的xshell客户端,并设置了编码格式为utf8。所以字符 ‘我’ 在发送给服务器的请求中的字节形式就是: 0xE68891
注意:如果使用的是可视化工具,比如securecrt/xshell之类的,这些工具可以使用自定义的字符集来编码发送到服务器的字符串,而不采用操作系统默认的字符集。
- 服务器接收到客户端发送来的请求其实是一串二进制的字节,它会认为这串字节采用的字符集是 character_set_client ,然后把这串字节转换为 character_set_connection 字符集编码的字符。
由于我的计算机上 character_set_client 的值是 utf8 ,首先会按照 utf8 字符集对字节串 0xE68891 进行解码,得到的字符串就是 ‘我’ ,然后按照 character_set_connection 代表的字符集,也就是 gbk 进行编码,得到的结果就是字节串 0xCED2 。 - 因为表 test 的列 name 采用的是 gbk 字符集,与 character_set_connection 一致,所以直接到列中找字节值为 0xCED2 的记录,最后找到了一条记录。
注意:如果某个列使用的字符集和character_set_connection代表的字符集不一致的话,还需要进行一次字符集转换。 - 上一步骤找到的记录中的 name 列其实是一个字节串 0xCED2 ,name列是采用 gbk 进行编码的,所以首先会将这个字节串使用 gbk 进行解码,得到字符串 ‘我’ ,然后再把这个字符串使用 character_set_results 代表 的字符集,也就是 utf8 进行编码,得到了新的字节串: 0xE68891 ,然后发送给客户端。
- 由于客户端是用的字符集是 utf8 ,所以可以顺利的将 0xE68891 解释成字符‘我’ ,从而显示到我们的显示器上,所以我们使用者也正确读懂了返回的结果。
总的可以参照下面这个图来总结以上的这几个步骤:
上面这个图中我们需要注意以下几个地方:
- 服务器认为客户端发送过来的请求是用 character_set_client 编码的。假设你的客户端采用的字符集和 character_set_client 不一样的话(也就是编码和解码的规则不一样),这就会出现意想不到的情况。比如我的客户端使用的是 utf8 字符集,如果把系统变量 character_set_client 的值设置为 ascii 的话,服务器可能无法理解我们发送的请求,更别谈处理这个请求了。
- 服务器将把得到的结果集使用 character_set_results 编码后发送给客户端。
假设你的客户端采用的字符集和 character_set_results 不一样的话,这就可能会出现客户端无法解码结果 集的情况,结果就是在你的屏幕上出现乱码。比如我的客户端使用的是 utf8 字符集,如果把系统变量character_set_results 的值设置为 ascii 的话,可能会产生乱码。 - character_set_connection 只是服务器在将请求的字节串从 character_set_client 转换为 character_set_connection 时使用,它是什么其实没多重要,但是一定要注意,该字符集包含的字符范围一定要涵盖请求中的字符,要不然会导致有的字符无法使用character_set_connection 代表的字符集进行编码。比如你把 character_set_client 设置为 utf8 ,把 character_set_connection 设置成 ascii ,那么此时你如果从客户端发送一个汉字到服务器,那么服务器无法使用 ascii 字符集来编码这个汉字,就会向用户发出一个警告。
了解了在 MySQL 中从发送请求到返回结果过程里发生的各种字符集转换,但是为啥要这样转来转去的呢?不觉得很繁琐么?
答:是的很繁琐,所以我们通常都把 character_set_client 、character_set_connection、 character_set_results 这三个系统变量设置成和客户端使用的字符集一致的情况,这样减少了很多无谓的字符集转换。为了方便我们设置, MySQL 提供了一条非常简便的语句:
SET NAMES 字符集名;
这一条语句产生的效果和我们执行这3条的效果是一样的:
SET character_set_client = 字符集名;
SET character_set_connection = 字符集名;
SET character_set_results = 字符集名;
比方说我的客户端使用的是 utf8 字符集,所以需要把这几个系统变量的值都设置为 utf8 :
mysql> SET names utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'character_set_%';
+--------------------------+----------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | gb2312 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/charsets/ |
+--------------------------+----------------------------------+
8 rows in set (0.01 sec)
注意: 如果使用的是Windows系统作为客户端,也就是没有用其他的终端软件,如xshell,那应该设置成gbk。
另外,如果你想在启动客户端的时候就把 character_set_client 、 character_set_connection 、 character_set_results 这三个系统变量的值设置成一样的,那我们可以在启动客户端的时候指定一个叫 default-character-set 的启动选项,比如在配置文件里可以这么写:
[client]
default-character-set=utf8
它起到的效果和执行一遍 SET NAMES utf8 是一样一样的,都会将那三个系统变量的值设置成 utf8 。
比较规则的应用
比较规则的作用通常体现比较字符串大小的表达式以及对某个字符串列进行排序,所以有时候也称为排序规则 。比方说表 test 的列 name 使用的字符集是 gbk ,使用的比较规则是 gbk_chinese_ci ,我们向里边插入几条记录:
mysql> INSERT INTO test(name) VALUES('a'), ('b'), ('A'), ('B');
Query OK, 4 rows affected (0.04 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select * from test order by name;
+------+
| name |
+------+
| a |
| A |
| b |
| B |
| 我 |
+------+
5 rows in set (0.00 sec)
mysql>
可以看到在默认的比较规则 gbk_chinese_ci 中是不区分大小写的,我们现在把列 name 的比较规则修改为 gbk_bin :
mysql> ALTER TABLE test MODIFY name VARCHAR(10) COLLATE gbk_bin;
Query OK, 5 rows affected (0.17 sec)
Records: 5 Duplicates: 0 Warnings: 0
由于 gbk_bin 是直接比较字符的编码,所以是区分大小写的,我们再看一下排序后的查询结果:
mysql> select * from test order by name;
+------+
| name |
+------+
| A |
| B |
| a |
| b |
| 我 |
+------+
5 rows in set (0.01 sec)
所以如果以后大家在对字符串做比较或者对某个字符串列做排序操作时没有得到想象中的结果,需要思考一下是不是字符规则的问题。
总结:总的来说,在选用字符集时要选择能支持表示字符的字符集,并尽量精简。其次,使用时,尽量保证客户端服务端(包括服务器级别,数据库级别,表级别,列级别)字符集及比较规则一致,避免进行多次转化及转换出错。
更多关于mysql的知识分享,请前往博客主页。编写过程中,难免出现差错,敬请指出