最近遇到一个关于MySQL字符乱码的问题,比较好解决,但是为了更好地理解MySQL中的字符系统,参考MySQL的文档整理一下关于MySQL中字符集合字符序的问题。
1. 什么是字符集和字符序
MySQL是存储数据的数据库,既然是存储数据的,那么数据就需要一个编码的规则,因为计算机对于所有的数据都是通过二进制编码的方式存储的。字符集(Character Sets),就是字符到二进制编码映射的集合。由于不同的语言所包含的字符不同,所以为了支持不同的语言,MySQL定义了许许多多不同的字符集。
举一个简单的例子,假如我们有一种语言只有四个字符:A, B, a, b
。
我们规定了这四个字符到二进制编码的规则:
Symbol | Encoding |
---|---|
A |
0 |
B |
1 |
a |
2 |
b |
3 |
在这里,A
就是一个字符,而数字0就是字符A
的编码,这四个字符和编码组合在一起就是字符集。
接下来看看什么是字符序(Collations)。
对于上面的字符集,我们需要有一个比较的规则。比如,我们想比较A
和B
,当然结果有三种:等于、小于或大于。但我们熟悉的还是A<B
。一种简单的方式就是比较他们的编码数字。因为0<1
,所以对应的字符A<B
。
这样就构成了一条字符比较的规则。这样的一条规则就可以构成一个字符序。
进一步,如果我们规定大写字母和对应的小写字母相等呢?这样就又多了一条规则:
a
=A
,b
=B
这样,两条比较的规则就又构成了一个字符序。
通过这个简单的例子,我们可以知道:
- 字符集就是字符到二进制编码的映射集合;
- 字符序是比较字符集的规则集合;
- 一个字符集可能有多个字符序。
在MySQL中,定义了许多字符集合字符序。这些字符集合字符序可以帮助我们处理很多字符相关的问题。
2. MySQL中的字符集合字符序
在MySQL中,我们可以使用如下的语句来查看字符集:
SHOW CHARACTER SET;
这会列出所有的字符集。如果想筛选的话,可以加上LIKE
条件:
SHOW CHARATER SET LIKE 'utf%';
结果如下:
+---------+------------------+--------------------+--------+
| Charset | Description | Default collation | Maxlen |
+---------+------------------+--------------------+--------+
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_0900_ai_ci | 4 |
+---------+------------------+--------------------+--------+
结果返回了字符集的名称、描述、默认字符序以及单个字符最大长度等信息。
每一个字符集都有一个默认的字符序,有的字符集有多个字符序,可以通过下面的语句查看一个字符集的字符序:
SHOW COLLATION WHERE Charset = 'utf8mb4';
返回的结果会列出所有的字符序以及相关的信息,这里就不展示了。
字符序有下面的特点:
- 两个不同的字符集不会有相同的字符序。也就是每个字符集都有自己单独的字符序小弟,大家不会有交集;
- 每一个字符集都有一个默认的字符序;
- 字符序的名字是以对应的字符集名称为前缀的。
3. 在MySQL中使用字符集
MySQL中有很多可以控制字符集的选项,这些选项过于复杂很容易混淆。不过记住一点:
只有基于字符的值才真正的“有”字符集的概念。
对于其他类型的值,字符集只是一个设置,用来指定用哪一种字符进行比较或操作。基于字符的值能存放在某列中、查询的字符中、表达式的计算结果中或者某个用户变量中,等等。
MySQL中字符集的设置可以分为两类:创建对象时的默认值、在服务器和客户端通信时的设置。
3.1 创建对象时的默认设置
在MySQL中,从上到下一共有四层字符集的设置,分别是服务器(Server)、数据库(Database)、表(Table)和列(Column),在每一层都可以指定一个默认的字符集:
其中,服务器和数据库的字符集设置有对应的参数:character_set_server
和character_set_database
,表和列的字符集设置在对应的DDL语句中。
由于真正存放数据的是列,所以更高层次的字符集设置仅仅是指定一个默认值,如果在创建列时没有知道字符集,就会从下到上寻找设置的字符集。如果指定了一个字符集,那么上面所有层次的设置都没有效果了。
在MySQL 5.5、5.6和5.7中,默认的字符集是latin1,在最新的MySQL 8中,默认的字符集是utf8。
3.2 服务器和客户端通信时的设置
当服务器和客户端进行通信时,可能各自使用不同的字符集。这时,服务器需要进行字符的转换工作。这涉及到MySQL中的三个参数:character_set_client
、character_set_connection
和character_set_results
。
这三个参数的影响效果如下图所示:
这里服务器进行了两次翻译过程:
- SQL语句从客户端离开时的字符集是
character_set_client
; - SQL语句进入服务器后服务器转换成
character_set_connection
; - 服务器处理完SQL语句后,将结果的字符集设置成了
character_set_results
。
根据需要,可以使用SET NAMES
或者SET CHARACTER SET
语句来改变上面的设置:
SET NAMES 'utf8';
这样,会将character_set_client
、character_set_connection
和character_set_results
都设置成utf8。
不过在服务器上使用这个命令只能改变服务器端的设置,客户端程序也需要设置正确的字符集才能避免出现问题。
3.3 小结
可以通过SHOW VARIABLES LIKE 'character%'
命令来查看这些参数的值:
+--------------------------+-----------------------------------------------------------+
| Variable_name | Value |
+--------------------------+-----------------------------------------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql-8.0.13-macos10.14-x86_64/share/charsets/ |
+--------------------------+-----------------------------------------------------------+
4. 字符集的选择
使用不同的字符集会带来更多的CPU操作,可能也会消耗更多的内存和磁盘空间。因此,为了方便,最好先为服务器(或者数据库)设置一个合理的字符集,然后根据不同的情况让某些列选择合适的字符集,
如果统一使用utf8字符集,整个世界都清净了。这也是很常见的一种做法。不过有的时候并不需要使用utf8,使用utf8之后会增加磁盘空间的消耗。