# 简介

计算机中存储的实际上是二进制数据,想要存储字符串,就要建立字符与二进制数据的映射关系。将字符映射为二进制数据的过程叫做编码,将二进制数据映射为字符的过程就是解码

为了容易记忆,你需要知道,解码就是把看不懂的数据(二进制数据)解析为看得懂的数据(字符)。

因为每个字符实际上是二进制数据,所以他们是可以比较大小的,由此衍生出比较规则。以最经典的 ASCII 字符集为例,该字符集一共收录 128 个字符每个字符由一个字节保存。字符 a 的二进制编码为 1100001 ,字符 b 的二进制编码为 1100010 ,通过比较就可以看出 a>b

需要记住几种重要的字符集:

  • GBK 字符集:在字符范围上对 GB2312 字符集进行扩充,编码方式兼容 GB2312 字符集。记住其应用广泛即可
  • UTF-8 字符集:几乎收录各国(地区)使用的字符,并且在不断扩充。使用变长编码方式,一个字符需要使用 1~4 个字节

其实 UTF-8 只是 Unicode 字符集的一种编码方案,其他的还有 UTF-16(2 或 4 字节编码一个字符),UTF-32(4 字节)。

# MySQL 支持的字符集与比较规则

# utf8mb3 和 utf8mb4

我们平常使用的字符一般用 1~3 个字节就可以表示,在 MySQL 中,字符集表示一个字符所用的最大字节数会影响系统存储和性能(比如一条记录中变长数据的存储,之后的文章会讲到)。

总之,你需要记住的是 UTF-8 使用 1-4 个字节存储方式有点浪费,所以就有了 utf8mb3。

  • utf8mb3:阉割版的 UTF-8 字符集,使用 1~3 个字节表示一个字符。
  • utf8mb4:就是 UTF-8 字符集,使用 1~4 个字节。

在 MySQL 中,utf8 是 utf8mb3 的别名。同时,在 MySQL8.0 中,utf8mb4 被极大优化,成为了默认的字符集。

# 字符集的查看

mysql> show charset;
+----------+---------------------------------+---------------------+--------+
| 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 |
| utf8     | UTF-8 Unicode                   | utf8_general_ci     |      3 |
+----------+---------------------------------+---------------------+--------+

命令 show (character set | charset) [like 匹配模式];[] 里的内容可以省略)

Maxlen 表示该字符集最多需要几个字节表示一个字符。

表 1 常用字符集与 Maxlen

CharsetMaxlen
ascii1
latin11
gb23122
gbk2
utf83
utf8mb44

Default collation 表示默认的比较规则,一个字符集可能对应多种比较规则

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_icelandic_ci        | utf8    | 193 |         | Yes      |       8 |
| utf8_latvian_ci          | utf8    | 194 |         | Yes      |       8 |
| utf8_romanian_ci         | utf8    | 195 |         | Yes      |       8 |
| utf8_slovenian_ci        | utf8    | 196 |         | Yes      |       8 |
| utf8_polish_ci           | utf8    | 197 |         | Yes      |       8 |
| utf8_estonian_ci         | utf8    | 198 |         | Yes      |       8 |
| utf8_spanish_ci          | utf8    | 199 |         | Yes      |       8 |
| utf8_swedish_ci          | utf8    | 200 |         | Yes      |       8 |
| utf8_turkish_ci          | utf8    | 201 |         | Yes      |       8 |
| utf8_czech_ci            | utf8    | 202 |         | Yes      |       8 |
| utf8_danish_ci           | utf8    | 203 |         | Yes      |       8 |
| utf8_lithuanian_ci       | utf8    | 204 |         | Yes      |       8 |
| utf8_slovak_ci           | utf8    | 205 |         | Yes      |       8 |
| utf8_spanish2_ci         | utf8    | 206 |         | Yes      |       8 |
| utf8_roman_ci            | utf8    | 207 |         | Yes      |       8 |
| utf8_persian_ci          | utf8    | 208 |         | Yes      |       8 |
| utf8_esperanto_ci        | utf8    | 209 |         | Yes      |       8 |
| utf8_hungarian_ci        | utf8    | 210 |         | Yes      |       8 |
| utf8_sinhala_ci          | utf8    | 211 |         | Yes      |       8 |
| utf8_german2_ci          | utf8    | 212 |         | Yes      |       8 |
| utf8_croatian_ci         | utf8    | 213 |         | Yes      |       8 |
| utf8_unicode_520_ci      | utf8    | 214 |         | Yes      |       8 |
| utf8_vietnamese_ci       | utf8    | 215 |         | Yes      |       8 |
| utf8_general_mysql500_ci | utf8    | 223 |         | Yes      |       1 |
+--------------------------+---------+-----+---------+----------+---------+
27 rows in set (0.00 sec)

命令: show collation [like 匹配模式];

从命名中可以看出有些与地域有关系,如 utf8_spanish_ci 是西班牙语的比较规则, utf8_polish_ci 则是波兰语的比较规则。

表 2 比较规则后缀英文释义及描述

后缀英文释义描述
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase (大小写) sensitive区分大小写
_binbinary以二进制方式比较

# 应用

MySQL 有四个级别的字符集和比较规则,分别是服务器级别,数据库级别,表级别,列级别。下面解释如何设置和查看不同级别的字符集和比较规则。

# 服务器级别

MySQL 提供两个变量来表示服务器级别的字符集和比较规则。

表 3 服务器对应的系统变量

系统变量描述
character_set_server服务器级别的字符集
collation_server服务器级别的比较规则

相关命令

mysql> show variables like '%_server';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| character_set_server | latin1            |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
2 rows in set, 1 warning (0.00 sec)

如果想要修改这两个系统变量,需要通过启动选项或者使用 set 语句修改 set global/session varName = value (如果看不懂命令,之后的文章会提到 global/session

# 数据库级别

字符集和比较规则在创建和修改数据库时可以指定该数据库的字符集和比较规则

create database 数据库名
	[[default] character set 字符集名称]
	[[default] collation 比较规则名称]
	
alter database 数据库名
	[[default] character set 字符集名称]
	[[default] collation 比较规则名称]

数据库级别也有两个系统变量,当我们使用 use 语句选择一个数据库时,这两个系统变量的值就是该数据库的字符集和比较规则。如果没有选择数据库,它们的值和服务器级别的系统变量值相同

表 4 数据库对应的系统变量

系统变量描述
character_set_database当前数据库的字符集
collation_databased当前数据库的比较规则

我们不能通过修改这两个系统变量的值来改变当前数据库的字符集和比较规则。

mysql> show variables like '%_server';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| character_set_server | latin1            |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> create database test;
Query OK, 1 row affected (0.01 sec)

mysql> use test;
Database changed

mysql> show variables like '%_database';
+------------------------+-------------------+
| Variable_name          | Value             |
+------------------------+-------------------+
| character_set_database | latin1            |
| collation_database     | latin1_swedish_ci |
| skip_show_database     | OFF               |
+------------------------+-------------------+
3 rows in set, 1 warning (0.00 sec)

可以看到,如果在创建数据库时没有指定字符集和比较规则,就会使用服务器级别的字符集和比较规则

# 表级别

表级别和数据库级别差不多,都是在创建,修改表时可以修改表的字符集和比较规则,如果没有在创建表时指明其字符集和比较规则,就会使用当前数据库的字符集和比较规则。

create table t(
	col varchar(10)
) character set utf8,collate utf8_general_ci;

alter table t character set utf8;

表查看比较规则

# 使用\G是将输出结果旋转90度输出,方便观看
mysql> show table status from test like 't' \G;
*************************** 1. row ***************************
           Name: t
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2022-07-24 17:44:59
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci #比较规则
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

ERROR:
No query specified

可以看到,表 t 的比较规则为 utf8_general_ci ,则字符集为 utf8 (为什么知道比较规则之后就知道了字符集,请继续看)。

# 列级别

同一个表中,不同列也可以有不同的比较规则。

create table 表名(
	列名 字符串类型 [character set 字符集名称] [collate 比较规则名称],
    其他列....
);

alter table 表名 modify 列名 [character set 字符集名称] [collate 比较规则名称];

需要注意的是,对于任何一个级别,存在两条修改规则:

  • 只修改字符集,则比较规则将会变为修改后的字符集默认的比较规则。
  • 只修改比较规则,则字符集会变为比较规则对应的字符集。

如果想要同时修改字符集和比较规则,他们两个一定要适配,比如下列命令

mysql> alter table t character set utf8 collate utf8mb4_general_ci;
ERROR 1253 (42000): COLLATION 'utf8mb4_general_ci' is not valid for CHARACTER SET 'utf8'

报错信息为比较规则 utf8mb4_general_ci 对于字符集 utf8 是无效的,所以,我们知道了一个表的比较规则,一般也就知道了其字符集。

# 引用

本文总结于《从根上理解 MySQL》第三章 p32-p43,作者:小孩子 4919。对于有能力或者兴趣的读者,鄙人强烈推荐这本书,如果您能够通过正规渠道购买,支持作者,支持正版,支持每一颗热爱技术的心,我将感激不尽。