10305 字
约 34 分钟
6
从跟上理解 MySQL,彻底掌握MySQL的字符集和比较规则

从跟上理解 MySQL,彻底掌握MySQL的字符集和比较规则

作者:沉默王二
发布时间:2024-02-04T09:19:54.267+0800


昨天晚上写到晚上 12 点,今天又做了一点修改,感觉很多以为自己掌握的知识,真正讲给大家的时候,发现还是需要去验证和确认,比如说 utf8 和 utf16 的区别,比较规则到底有什么用处,MySQL 为什么要使用 utf8mb4 等等。

继续肝就对了,输出倒逼输入,希望和大家一起成长和坚持。

当我们创建**数据库 或者表 **的时候,就要指定字符集和比较规则。

那可能一些小伙伴会说,我创建的时候没指定啊,那是 MySQL 已经帮你默认了。

比如说,你可以通过这样的 SQL 语句来查询数据库默认的字符集和比较规则:

SHOW VARIABLES LIKE 'character_set_database';

其中的 utf8mb4 就是我本机 MySQL 默认的字符集,utf8mb4_0900_ai_ci 就是我本机 MySQL 默认的比较规则,它们具体代表了什么含义,我们随后会细致讲解。

不同的字符集支持不同的语言和符号,选择正确的字符集能够确保数据库能够准确存储和展示各种语言的字符,尤其是在处理国际化应用时。

在之前的版本中,MySQL 默认的还不是 utf8mb4 字符集,要存储 emoji 表情的时候,就需要特殊处理。

刚去查了一下,2017 年,我在 CSDN 上还发表过一篇文章,讲如何在 MySQL 中存储 emoji 表情,因为用户的微信昵称中会有 emoji 表情,当时这篇文章还帮助过不少 MySQL 的新人。

文章链接:mysql 微信用户昵称 emoji 完整保存

比较规则决定了数据库如何比较和排序字符串数据。这包括

  • 是否区分大小写(case-sensitive)?
  • 是否区分重音符号(accent-sensitive)?

不一致的比较规则可能导致意外的查询结果,比如说数据库是按照不区分大小写存储的,而查询的时候又需要区分大小写,那就会出现问题。

好,接下来,我们详细地来聊一聊字符集和比较规则。

字符集

在计算机的世界里,它是只认识 0 和 1 的,那我们人,日常接触最多的就是中文汉字、英文、数字、符号等等。

那计算机怎么把这些东西存储起来呢?

就需要我们定义一些规则,比如说 王二 存储到计算机里,是存储 000010001 呢?还是存储 000010010 呢?

这就需要字符集来定义了。

ASCII

一开始出现的字符集是 ASCII,共收录了 128 个字符,包括空格、标点符号、数字、大小写字母等等。

比如说空格的 ASCII 码是 32(十进制),二进制是 00100000;大写字母 A 的 ASCII 码是 65(十进制),二进制是 01000001。

大家可以通过下面这个程序验证下:

// 请输出 空格的 ASCII 码,以及对应的二进制

System.out.println((int) c);System.out.println(Integer.toBinaryString(c));
// 请输出 A 的 ASCII 码,以及对应的二进制c = 'A';System.out.println((int) c);System.out.println(Integer.toBinaryString(c));

把一个字符转成二进制数据库的过程叫编码,相应的,把二进制转成字符的过程叫解码。

ISO 8859-1

随着计算机的发展,大家发现 ASCII 字符集不够用了,于是就出现了 ISO 8859-1 字符集,它是 ASCII 的扩展,共收录了 256 个字符。

包括了 ASCII 字符集的 128 个字符,还增加了 128 个字符,包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。

来自维基百科

这个字符集还有一个别名叫做 Latin1,如果你看过 JDK9 以上 **String 源码 **的话,会注意到这个字符集。

这里的 StringLatin1 和 StringUTF16 是 JDK9 之后新增的,用来优化 String 类的内存占用。

一个字节是 8 位,256 个字符是完全可以用一个字节来表示的,所以 ISO 8859-1 字符集是单字节编码的。

显然会比 UTF-8、UTF-16 这些多字节编码要节省空间。

GB2312

那随着计算机的发展,ISO 8859-1 肯定也不够用了,于是就出现了 GB2312 字符集,收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。

该字符集由中国国家标准总局于 1980 年发布,于 1981 年 5 月 1 日开始实施,标准号是GB/T 2312-1980,这也是这套字符集的由来。

GB2312 当然是兼容 ASCII 的,所以它的编码分两种:

  • 单字节编码:如果该字节在 ASCII 字符集内,用一个字节表示一个字符;
  • 双字节编码:如果该字节不在 ASCII 字符集内,用两个字节表示一个字符。

GBK

汉字博大精深,GB2312 字符集也不够用了,于是就出现了 GBK 字符集,它是 GB2312 的扩展,收录了 21003 个汉字,以及图形符号 883 个。

以前敲代码的时候,经常出现**中文乱码 **,其实就是因为编码不一致导致的。最著名的就是拷锟斤,见下图。

Unicode

Unicode 字符集的出现,解决了全世界字符编码混乱的局面,中文译作“万国码”、“国际码”,还是挺贴切的。

Unicode 虽然统一了全世界字符的编码,但却没有规定如何存储这些字符,于是就有了 UTF-8、UTF-16、UTF-32 等等。

UTF-8

一种变长编码,用 1~4 个字节来表示一个字符,英文字符用 1 个字节表示,中文字符用 3 个字节表示。也是互联网上最常用的编码方式。

UTF-16

根据字符所在的码点范围,决定使用 2 个字节(16 位)还是 4 个字节来进行编码。

当字符的 Unicode 码点在 U+0000 到 U+FFFF 之间时,使用 2 个字节进行编码;这个范围包括了大部分常用字符,如大多数国家的字母、标点符号和符号,被称为基本多文种平面(BMP)。

当字符的 Unicode 码点在 U+10000 到 U+10FFFF 之间时,使用 4 个字节进行编码。这个范围包括了辅助字符,如古文、象形文字以及未来可能加入的新字符。

假设我们要编码 U+1F600(😀 表情符号),它超出了基本多文种平面的范围。根据 UTF-16 的编码规则,这个字符会被编码为一对代理项,分别占用 16 位,总共占用 32 位(4 个字节)。

UTF-32

UTF-32 是一种固定长度编码,用 4 个字节来表示一个字符。不考虑空间占用,UTF-32 是最简单的编码方式,因为不需要考虑变长编码的问题嘛。

UTF-8MB4

具体不确定是哪个 MySQL 版本(据说是 5.5.3 之后的版本,我没去验证),引入了 utf8mb4 字符集,它是 utf8 的超集,支持 4 字节的字符。

mb 就是 most bytes 4 的意思。

utf8mb4 之所以存在,就是因为之前的 MySQL 版本,在实现 utf8 字符集的时候,只支持最大 3 个字节的字符编码,这限制了 MySQL 在处理某些 Unicode 字符的能力,比如说大部分 emoji 表情,一些罕见的文字和符号等。

utf8mb4 通过允许字符使用最多 4 个字节的编码,解决了这个问题。

utf8mb4 编码的字符长度范围如下:

  • 1 字节:标准 ASCII 字符直接使用一个字节表示,保持与 ASCII 编码的兼容。
  • 2 字节:用于编码拉丁字符扩展、希腊字母、西里尔字母、亚美尼亚字母、希伯来字母、阿拉伯字母等。
  • 3 字节:用于编码基本多文种平面(BMP)之外的字符,如中文、日文、韩文。
  • 4 字节:用于编码超出 BMP 的字符,包括表情符号、一些罕见的或古老的文字系统,以及新加入 Unicode 的字符。

那从 MySQL 8 开始,utf8mb4 就是 MySQL 默认的字符集了。

那通过 show charset 命令,我们可以看到 MySQL 支持的字符集。我本机 MySQL 目前一共支持 41 种字符集。

字符集级别

MySQL 一共有个四个级别的字符集,分别是:

  • 服务器级别:通过 show variables like 'character_set_server' 命令查看。
  • 数据库级别:通过 show create database 数据库名 命令查看。
  • 表级别:通过 show create table 表名 命令查看。
  • 字段级别:定义字段的时候指定,比如 name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci。但其实并不常用,因为我们一般都采用统一的字符集。

字符集转换

前面我们聊过,MySQL 是 C/S 架构的,客户端发往服务器的请求本质上是一个字符串,服务器返回给客户端的结果也是一个字符串,而字符串其实是使用某种字符集编码的二进制数据。

从发送请求到返回结果,中间涉及到多个字符集的转换,这个过程会涉及到 3 个系统变量:

  • character_set_client:服务器解码请求时使用的字符集。
  • character_set_connection:服务器处理请求时会把请求字符串从 character_set_client 转换成 character_set_connection。
  • character_set_results:服务器向客户端返回数据时使用的字符集。

我们来查看一下这三个系统变量的值:

SHOW VARIABLES LIKE 'character_set_client';

SHOW VARIABLES LIKE 'character_set_results';

我本机 MySQL 的默认值如下:

都是 utf8,这也保证了字符集的一致性。

比较规则

前面我们也简单说了比较规则,它决定了数据库如何比较和排序字符串数据。

接下来,我们就来举一个实际的例子,来验证比较规则的影响。

第一步,我们创建一个测试表,使用不同的比较规则来看看如何影响数据的排序和比较。

-- 使用大小写不敏感的比较规则
name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 使用大小写敏感的比较规则CREATE TABLE users_cs ( name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

  • utf8mb4_general_ci:大小写不敏感的比较规则。
  • utf8mb4_bin:大小写敏感的比较规则。

第二步,插入相同的测试数据,包括大小写不同的数据。

INSERT INTO users_ci (name) VALUES ('Java'), ('java'), ('JAVA');

第三步,查询数据,看看排序和比较的结果。

-- 从大小写不敏感的表查询

-- 从大小写敏感的表查询SELECT name FROM users_cs ORDER BY name;

  • 大小写不敏感的查询结果 (users_ci 使用的是 utf8mb4_general_ci 比较规则):所有名称将按字母顺序排序,不区分大小写。
  • 大小写敏感的查询结果 (users_cs 使用的是 utf8mb4_bin 比较规则):大小写不同的名称将被视为不同的值,并且在排序时会区分大小写。先是大写 J 然后是小结 j。

在**技术派实战项目 **中,我们并没有明确指定比较规则,采用的是 MySQL 默认的比较规则。

MySQL 目前支持的比较规则非常多,我们通过 show collation like 'utf8mb4%' 命令,看一下 utf8 字符集下的比较规则吧。

我本机 MySQL 能看到的是 69 种,默认的是 utf8mb4_0900_ai_ci,也就是不区分重音,不区分大小写的比较规则。

那具体这些后缀(ai、ci、bin、cs、as)代表的是什么规则呢? 见下表。

在重音敏感的比较规则下,"a" 和 "á" 被视为不同的字符。重音对于我们中文环境来说,没啥意义,主要用于希腊语(1982年前)、法语、加泰罗尼亚语、威尔士语、意大利语、越南语、苏格兰盖尔语、挪威语、葡萄牙语等。

在大小写敏感的比较规则中,字母 "A" 和 "a" 被视为不同的字符。如果应用主要处理英文数据,则可能需要一个大小写敏感的比较规则。对于多语言应用,则可能需要一个更通用的比较规则,如 utf8mb4_unicode_ci。

utf8mb4_bin 意味着比较是基于字符的二进制值,使用这种比较规则,字符串的比较和排序将完全区分大小写,并且对于任何字符编码的微小差异都非常敏感。

小结

  • 字符集决定了数据库能够存储和展示哪些字符。
  • 比较规则决定了数据库如何比较和排序字符串数据。

在创建数据库和表的时候,我们需要指定字符集和比较规则,这样能够确保数据库能够准确存储和展示各种语言的字符。

MySQL 默认的字符集是 utf8mb4,比较规则是 utf8mb4_0900_ai_ci,这个比较规则是不区分重音,不区分大小写的。

昨天晚上写到晚上 12 点,今天又做了一点修改,感觉很多以为自己掌握的知识,真正讲给大家的时候,发现还是需要去验证和确认,比如说 utf8 和 utf16 的区别,比较规则到底有什么用处,MySQL 为什么要使用 utf8mb4 等等。

继续肝就对了,输出倒逼输入,希望和大家一起成长和坚持。

当我们创建**数据库 或者表 **的时候,就要指定字符集和比较规则。

那可能一些小伙伴会说,我创建的时候没指定啊,那是 MySQL 已经帮你默认了。

比如说,你可以通过这样的 SQL 语句来查询数据库默认的字符集和比较规则:

SHOW VARIABLES LIKE 'character_set_database';

其中的 utf8mb4 就是我本机 MySQL 默认的字符集,utf8mb4_0900_ai_ci 就是我本机 MySQL 默认的比较规则,它们具体代表了什么含义,我们随后会细致讲解。

不同的字符集支持不同的语言和符号,选择正确的字符集能够确保数据库能够准确存储和展示各种语言的字符,尤其是在处理国际化应用时。

在之前的版本中,MySQL 默认的还不是 utf8mb4 字符集,要存储 emoji 表情的时候,就需要特殊处理。

刚去查了一下,2017 年,我在 CSDN 上还发表过一篇文章,讲如何在 MySQL 中存储 emoji 表情,因为用户的微信昵称中会有 emoji 表情,当时这篇文章还帮助过不少 MySQL 的新人。

文章链接:mysql 微信用户昵称 emoji 完整保存

比较规则决定了数据库如何比较和排序字符串数据。这包括

  • 是否区分大小写(case-sensitive)?
  • 是否区分重音符号(accent-sensitive)?

不一致的比较规则可能导致意外的查询结果,比如说数据库是按照不区分大小写存储的,而查询的时候又需要区分大小写,那就会出现问题。

好,接下来,我们详细地来聊一聊字符集和比较规则。

字符集

在计算机的世界里,它是只认识 0 和 1 的,那我们人,日常接触最多的就是中文汉字、英文、数字、符号等等。

那计算机怎么把这些东西存储起来呢?

就需要我们定义一些规则,比如说 王二 存储到计算机里,是存储 000010001 呢?还是存储 000010010 呢?

这就需要字符集来定义了。

ASCII

一开始出现的字符集是 ASCII,共收录了 128 个字符,包括空格、标点符号、数字、大小写字母等等。

比如说空格的 ASCII 码是 32(十进制),二进制是 00100000;大写字母 A 的 ASCII 码是 65(十进制),二进制是 01000001。

大家可以通过下面这个程序验证下:

// 请输出 空格的 ASCII 码,以及对应的二进制

System.out.println((int) c);System.out.println(Integer.toBinaryString(c));
// 请输出 A 的 ASCII 码,以及对应的二进制c = 'A';System.out.println((int) c);System.out.println(Integer.toBinaryString(c));

把一个字符转成二进制数据库的过程叫编码,相应的,把二进制转成字符的过程叫解码。

ISO 8859-1

随着计算机的发展,大家发现 ASCII 字符集不够用了,于是就出现了 ISO 8859-1 字符集,它是 ASCII 的扩展,共收录了 256 个字符。

包括了 ASCII 字符集的 128 个字符,还增加了 128 个字符,包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。

来自维基百科

这个字符集还有一个别名叫做 Latin1,如果你看过 JDK9 以上 **String 源码 **的话,会注意到这个字符集。

这里的 StringLatin1 和 StringUTF16 是 JDK9 之后新增的,用来优化 String 类的内存占用。

一个字节是 8 位,256 个字符是完全可以用一个字节来表示的,所以 ISO 8859-1 字符集是单字节编码的。

显然会比 UTF-8、UTF-16 这些多字节编码要节省空间。

GB2312

那随着计算机的发展,ISO 8859-1 肯定也不够用了,于是就出现了 GB2312 字符集,收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。

该字符集由中国国家标准总局于 1980 年发布,于 1981 年 5 月 1 日开始实施,标准号是GB/T 2312-1980,这也是这套字符集的由来。

GB2312 当然是兼容 ASCII 的,所以它的编码分两种:

  • 单字节编码:如果该字节在 ASCII 字符集内,用一个字节表示一个字符;
  • 双字节编码:如果该字节不在 ASCII 字符集内,用两个字节表示一个字符。

GBK

汉字博大精深,GB2312 字符集也不够用了,于是就出现了 GBK 字符集,它是 GB2312 的扩展,收录了 21003 个汉字,以及图形符号 883 个。

以前敲代码的时候,经常出现**中文乱码 **,其实就是因为编码不一致导致的。最著名的就是拷锟斤,见下图。

Unicode

Unicode 字符集的出现,解决了全世界字符编码混乱的局面,中文译作“万国码”、“国际码”,还是挺贴切的。

Unicode 虽然统一了全世界字符的编码,但却没有规定如何存储这些字符,于是就有了 UTF-8、UTF-16、UTF-32 等等。

UTF-8

一种变长编码,用 1~4 个字节来表示一个字符,英文字符用 1 个字节表示,中文字符用 3 个字节表示。也是互联网上最常用的编码方式。

UTF-16

根据字符所在的码点范围,决定使用 2 个字节(16 位)还是 4 个字节来进行编码。

当字符的 Unicode 码点在 U+0000 到 U+FFFF 之间时,使用 2 个字节进行编码;这个范围包括了大部分常用字符,如大多数国家的字母、标点符号和符号,被称为基本多文种平面(BMP)。

当字符的 Unicode 码点在 U+10000 到 U+10FFFF 之间时,使用 4 个字节进行编码。这个范围包括了辅助字符,如古文、象形文字以及未来可能加入的新字符。

假设我们要编码 U+1F600(😀 表情符号),它超出了基本多文种平面的范围。根据 UTF-16 的编码规则,这个字符会被编码为一对代理项,分别占用 16 位,总共占用 32 位(4 个字节)。

UTF-32

UTF-32 是一种固定长度编码,用 4 个字节来表示一个字符。不考虑空间占用,UTF-32 是最简单的编码方式,因为不需要考虑变长编码的问题嘛。

UTF-8MB4

具体不确定是哪个 MySQL 版本(据说是 5.5.3 之后的版本,我没去验证),引入了 utf8mb4 字符集,它是 utf8 的超集,支持 4 字节的字符。

mb 就是 most bytes 4 的意思。

utf8mb4 之所以存在,就是因为之前的 MySQL 版本,在实现 utf8 字符集的时候,只支持最大 3 个字节的字符编码,这限制了 MySQL 在处理某些 Unicode 字符的能力,比如说大部分 emoji 表情,一些罕见的文字和符号等。

utf8mb4 通过允许字符使用最多 4 个字节的编码,解决了这个问题。

utf8mb4 编码的字符长度范围如下:

  • 1 字节:标准 ASCII 字符直接使用一个字节表示,保持与 ASCII 编码的兼容。
  • 2 字节:用于编码拉丁字符扩展、希腊字母、西里尔字母、亚美尼亚字母、希伯来字母、阿拉伯字母等。
  • 3 字节:用于编码基本多文种平面(BMP)之外的字符,如中文、日文、韩文。
  • 4 字节:用于编码超出 BMP 的字符,包括表情符号、一些罕见的或古老的文字系统,以及新加入 Unicode 的字符。

那从 MySQL 8 开始,utf8mb4 就是 MySQL 默认的字符集了。

那通过 show charset 命令,我们可以看到 MySQL 支持的字符集。我本机 MySQL 目前一共支持 41 种字符集。

字符集级别

MySQL 一共有个四个级别的字符集,分别是:

  • 服务器级别:通过 show variables like 'character_set_server' 命令查看。
  • 数据库级别:通过 show create database 数据库名 命令查看。
  • 表级别:通过 show create table 表名 命令查看。
  • 字段级别:定义字段的时候指定,比如 name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci。但其实并不常用,因为我们一般都采用统一的字符集。

字符集转换

前面我们聊过,MySQL 是 C/S 架构的,客户端发往服务器的请求本质上是一个字符串,服务器返回给客户端的结果也是一个字符串,而字符串其实是使用某种字符集编码的二进制数据。

从发送请求到返回结果,中间涉及到多个字符集的转换,这个过程会涉及到 3 个系统变量:

  • character_set_client:服务器解码请求时使用的字符集。
  • character_set_connection:服务器处理请求时会把请求字符串从 character_set_client 转换成 character_set_connection。
  • character_set_results:服务器向客户端返回数据时使用的字符集。

我们来查看一下这三个系统变量的值:

SHOW VARIABLES LIKE 'character_set_client';

SHOW VARIABLES LIKE 'character_set_results';

我本机 MySQL 的默认值如下:

都是 utf8,这也保证了字符集的一致性。

比较规则

前面我们也简单说了比较规则,它决定了数据库如何比较和排序字符串数据。

接下来,我们就来举一个实际的例子,来验证比较规则的影响。

第一步,我们创建一个测试表,使用不同的比较规则来看看如何影响数据的排序和比较。

-- 使用大小写不敏感的比较规则
name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 使用大小写敏感的比较规则CREATE TABLE users_cs ( name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

  • utf8mb4_general_ci:大小写不敏感的比较规则。
  • utf8mb4_bin:大小写敏感的比较规则。

第二步,插入相同的测试数据,包括大小写不同的数据。

INSERT INTO users_ci (name) VALUES ('Java'), ('java'), ('JAVA');

第三步,查询数据,看看排序和比较的结果。

-- 从大小写不敏感的表查询

-- 从大小写敏感的表查询SELECT name FROM users_cs ORDER BY name;

  • 大小写不敏感的查询结果 (users_ci 使用的是 utf8mb4_general_ci 比较规则):所有名称将按字母顺序排序,不区分大小写。
  • 大小写敏感的查询结果 (users_cs 使用的是 utf8mb4_bin 比较规则):大小写不同的名称将被视为不同的值,并且在排序时会区分大小写。先是大写 J 然后是小结 j。

在**技术派实战项目 **中,我们并没有明确指定比较规则,采用的是 MySQL 默认的比较规则。

MySQL 目前支持的比较规则非常多,我们通过 show collation like 'utf8mb4%' 命令,看一下 utf8 字符集下的比较规则吧。

我本机 MySQL 能看到的是 69 种,默认的是 utf8mb4_0900_ai_ci,也就是不区分重音,不区分大小写的比较规则。

那具体这些后缀(ai、ci、bin、cs、as)代表的是什么规则呢? 见下表。

在重音敏感的比较规则下,"a" 和 "á" 被视为不同的字符。重音对于我们中文环境来说,没啥意义,主要用于希腊语(1982年前)、法语、加泰罗尼亚语、威尔士语、意大利语、越南语、苏格兰盖尔语、挪威语、葡萄牙语等。

在大小写敏感的比较规则中,字母 "A" 和 "a" 被视为不同的字符。如果应用主要处理英文数据,则可能需要一个大小写敏感的比较规则。对于多语言应用,则可能需要一个更通用的比较规则,如 utf8mb4_unicode_ci。

utf8mb4_bin 意味着比较是基于字符的二进制值,使用这种比较规则,字符串的比较和排序将完全区分大小写,并且对于任何字符编码的微小差异都非常敏感。

小结

  • 字符集决定了数据库能够存储和展示哪些字符。
  • 比较规则决定了数据库如何比较和排序字符串数据。

在创建数据库和表的时候,我们需要指定字符集和比较规则,这样能够确保数据库能够准确存储和展示各种语言的字符。

MySQL 默认的字符集是 utf8mb4,比较规则是 utf8mb4_0900_ai_ci,这个比较规则是不区分重音,不区分大小写的。

昨天晚上写到晚上 12 点,今天又做了一点修改,感觉很多以为自己掌握的知识,真正讲给大家的时候,发现还是需要去验证和确认,比如说 utf8 和 utf16 的区别,比较规则到底有什么用处,MySQL 为什么要使用 utf8mb4 等等。

继续肝就对了,输出倒逼输入,希望和大家一起成长和坚持。

当我们创建**数据库 或者表 **的时候,就要指定字符集和比较规则。

那可能一些小伙伴会说,我创建的时候没指定啊,那是 MySQL 已经帮你默认了。

比如说,你可以通过这样的 SQL 语句来查询数据库默认的字符集和比较规则:

SHOW VARIABLES LIKE 'character_set_database';

其中的 utf8mb4 就是我本机 MySQL 默认的字符集,utf8mb4_0900_ai_ci 就是我本机 MySQL 默认的比较规则,它们具体代表了什么含义,我们随后会细致讲解。

不同的字符集支持不同的语言和符号,选择正确的字符集能够确保数据库能够准确存储和展示各种语言的字符,尤其是在处理国际化应用时。

在之前的版本中,MySQL 默认的还不是 utf8mb4 字符集,要存储 emoji 表情的时候,就需要特殊处理。

刚去查了一下,2017 年,我在 CSDN 上还发表过一篇文章,讲如何在 MySQL 中存储 emoji 表情,因为用户的微信昵称中会有 emoji 表情,当时这篇文章还帮助过不少 MySQL 的新人。

文章链接:mysql 微信用户昵称 emoji 完整保存

比较规则决定了数据库如何比较和排序字符串数据。这包括

  • 是否区分大小写(case-sensitive)?
  • 是否区分重音符号(accent-sensitive)?

不一致的比较规则可能导致意外的查询结果,比如说数据库是按照不区分大小写存储的,而查询的时候又需要区分大小写,那就会出现问题。

好,接下来,我们详细地来聊一聊字符集和比较规则。

字符集

在计算机的世界里,它是只认识 0 和 1 的,那我们人,日常接触最多的就是中文汉字、英文、数字、符号等等。

那计算机怎么把这些东西存储起来呢?

就需要我们定义一些规则,比如说 王二 存储到计算机里,是存储 000010001 呢?还是存储 000010010 呢?

这就需要字符集来定义了。

ASCII

一开始出现的字符集是 ASCII,共收录了 128 个字符,包括空格、标点符号、数字、大小写字母等等。

比如说空格的 ASCII 码是 32(十进制),二进制是 00100000;大写字母 A 的 ASCII 码是 65(十进制),二进制是 01000001。

大家可以通过下面这个程序验证下:

// 请输出 空格的 ASCII 码,以及对应的二进制

System.out.println((int) c);System.out.println(Integer.toBinaryString(c));
// 请输出 A 的 ASCII 码,以及对应的二进制c = 'A';System.out.println((int) c);System.out.println(Integer.toBinaryString(c));

把一个字符转成二进制数据库的过程叫编码,相应的,把二进制转成字符的过程叫解码。

ISO 8859-1

随着计算机的发展,大家发现 ASCII 字符集不够用了,于是就出现了 ISO 8859-1 字符集,它是 ASCII 的扩展,共收录了 256 个字符。

包括了 ASCII 字符集的 128 个字符,还增加了 128 个字符,包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。

来自维基百科

这个字符集还有一个别名叫做 Latin1,如果你看过 JDK9 以上 **String 源码 **的话,会注意到这个字符集。

这里的 StringLatin1 和 StringUTF16 是 JDK9 之后新增的,用来优化 String 类的内存占用。

一个字节是 8 位,256 个字符是完全可以用一个字节来表示的,所以 ISO 8859-1 字符集是单字节编码的。

显然会比 UTF-8、UTF-16 这些多字节编码要节省空间。

GB2312

那随着计算机的发展,ISO 8859-1 肯定也不够用了,于是就出现了 GB2312 字符集,收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。

该字符集由中国国家标准总局于 1980 年发布,于 1981 年 5 月 1 日开始实施,标准号是GB/T 2312-1980,这也是这套字符集的由来。

GB2312 当然是兼容 ASCII 的,所以它的编码分两种:

  • 单字节编码:如果该字节在 ASCII 字符集内,用一个字节表示一个字符;
  • 双字节编码:如果该字节不在 ASCII 字符集内,用两个字节表示一个字符。

GBK

汉字博大精深,GB2312 字符集也不够用了,于是就出现了 GBK 字符集,它是 GB2312 的扩展,收录了 21003 个汉字,以及图形符号 883 个。

以前敲代码的时候,经常出现**中文乱码 **,其实就是因为编码不一致导致的。最著名的就是拷锟斤,见下图。

Unicode

Unicode 字符集的出现,解决了全世界字符编码混乱的局面,中文译作“万国码”、“国际码”,还是挺贴切的。

Unicode 虽然统一了全世界字符的编码,但却没有规定如何存储这些字符,于是就有了 UTF-8、UTF-16、UTF-32 等等。

UTF-8

一种变长编码,用 1~4 个字节来表示一个字符,英文字符用 1 个字节表示,中文字符用 3 个字节表示。也是互联网上最常用的编码方式。

UTF-16

根据字符所在的码点范围,决定使用 2 个字节(16 位)还是 4 个字节来进行编码。

当字符的 Unicode 码点在 U+0000 到 U+FFFF 之间时,使用 2 个字节进行编码;这个范围包括了大部分常用字符,如大多数国家的字母、标点符号和符号,被称为基本多文种平面(BMP)。

当字符的 Unicode 码点在 U+10000 到 U+10FFFF 之间时,使用 4 个字节进行编码。这个范围包括了辅助字符,如古文、象形文字以及未来可能加入的新字符。

假设我们要编码 U+1F600(😀 表情符号),它超出了基本多文种平面的范围。根据 UTF-16 的编码规则,这个字符会被编码为一对代理项,分别占用 16 位,总共占用 32 位(4 个字节)。

UTF-32

UTF-32 是一种固定长度编码,用 4 个字节来表示一个字符。不考虑空间占用,UTF-32 是最简单的编码方式,因为不需要考虑变长编码的问题嘛。

UTF-8MB4

具体不确定是哪个 MySQL 版本(据说是 5.5.3 之后的版本,我没去验证),引入了 utf8mb4 字符集,它是 utf8 的超集,支持 4 字节的字符。

mb 就是 most bytes 4 的意思。

utf8mb4 之所以存在,就是因为之前的 MySQL 版本,在实现 utf8 字符集的时候,只支持最大 3 个字节的字符编码,这限制了 MySQL 在处理某些 Unicode 字符的能力,比如说大部分 emoji 表情,一些罕见的文字和符号等。

utf8mb4 通过允许字符使用最多 4 个字节的编码,解决了这个问题。

utf8mb4 编码的字符长度范围如下:

  • 1 字节:标准 ASCII 字符直接使用一个字节表示,保持与 ASCII 编码的兼容。
  • 2 字节:用于编码拉丁字符扩展、希腊字母、西里尔字母、亚美尼亚字母、希伯来字母、阿拉伯字母等。
  • 3 字节:用于编码基本多文种平面(BMP)之外的字符,如中文、日文、韩文。
  • 4 字节:用于编码超出 BMP 的字符,包括表情符号、一些罕见的或古老的文字系统,以及新加入 Unicode 的字符。

那从 MySQL 8 开始,utf8mb4 就是 MySQL 默认的字符集了。

那通过 show charset 命令,我们可以看到 MySQL 支持的字符集。我本机 MySQL 目前一共支持 41 种字符集。

字符集级别

MySQL 一共有个四个级别的字符集,分别是:

  • 服务器级别:通过 show variables like 'character_set_server' 命令查看。
  • 数据库级别:通过 show create database 数据库名 命令查看。
  • 表级别:通过 show create table 表名 命令查看。
  • 字段级别:定义字段的时候指定,比如 name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci。但其实并不常用,因为我们一般都采用统一的字符集。

字符集转换

前面我们聊过,MySQL 是 C/S 架构的,客户端发往服务器的请求本质上是一个字符串,服务器返回给客户端的结果也是一个字符串,而字符串其实是使用某种字符集编码的二进制数据。

从发送请求到返回结果,中间涉及到多个字符集的转换,这个过程会涉及到 3 个系统变量:

  • character_set_client:服务器解码请求时使用的字符集。
  • character_set_connection:服务器处理请求时会把请求字符串从 character_set_client 转换成 character_set_connection。
  • character_set_results:服务器向客户端返回数据时使用的字符集。

我们来查看一下这三个系统变量的值:

SHOW VARIABLES LIKE 'character_set_client';

SHOW VARIABLES LIKE 'character_set_results';

我本机 MySQL 的默认值如下:

都是 utf8,这也保证了字符集的一致性。

比较规则

前面我们也简单说了比较规则,它决定了数据库如何比较和排序字符串数据。

接下来,我们就来举一个实际的例子,来验证比较规则的影响。

第一步,我们创建一个测试表,使用不同的比较规则来看看如何影响数据的排序和比较。

-- 使用大小写不敏感的比较规则
name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 使用大小写敏感的比较规则CREATE TABLE users_cs ( name VARCHAR(50)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

  • utf8mb4_general_ci:大小写不敏感的比较规则。
  • utf8mb4_bin:大小写敏感的比较规则。

第二步,插入相同的测试数据,包括大小写不同的数据。

INSERT INTO users_ci (name) VALUES ('Java'), ('java'), ('JAVA');

第三步,查询数据,看看排序和比较的结果。

-- 从大小写不敏感的表查询

-- 从大小写敏感的表查询SELECT name FROM users_cs ORDER BY name;

  • 大小写不敏感的查询结果 (users_ci 使用的是 utf8mb4_general_ci 比较规则):所有名称将按字母顺序排序,不区分大小写。
  • 大小写敏感的查询结果 (users_cs 使用的是 utf8mb4_bin 比较规则):大小写不同的名称将被视为不同的值,并且在排序时会区分大小写。先是大写 J 然后是小结 j。

在**技术派实战项目 **中,我们并没有明确指定比较规则,采用的是 MySQL 默认的比较规则。

MySQL 目前支持的比较规则非常多,我们通过 show collation like 'utf8mb4%' 命令,看一下 utf8 字符集下的比较规则吧。

我本机 MySQL 能看到的是 69 种,默认的是 utf8mb4_0900_ai_ci,也就是不区分重音,不区分大小写的比较规则。

那具体这些后缀(ai、ci、bin、cs、as)代表的是什么规则呢? 见下表。

在重音敏感的比较规则下,"a" 和 "á" 被视为不同的字符。重音对于我们中文环境来说,没啥意义,主要用于希腊语(1982年前)、法语、加泰罗尼亚语、威尔士语、意大利语、越南语、苏格兰盖尔语、挪威语、葡萄牙语等。

在大小写敏感的比较规则中,字母 "A" 和 "a" 被视为不同的字符。如果应用主要处理英文数据,则可能需要一个大小写敏感的比较规则。对于多语言应用,则可能需要一个更通用的比较规则,如 utf8mb4_unicode_ci。

utf8mb4_bin 意味着比较是基于字符的二进制值,使用这种比较规则,字符串的比较和排序将完全区分大小写,并且对于任何字符编码的微小差异都非常敏感。

小结

  • 字符集决定了数据库能够存储和展示哪些字符。
  • 比较规则决定了数据库如何比较和排序字符串数据。

在创建数据库和表的时候,我们需要指定字符集和比较规则,这样能够确保数据库能够准确存储和展示各种语言的字符。

MySQL 默认的字符集是 utf8mb4,比较规则是 utf8mb4_0900_ai_ci,这个比较规则是不区分重音,不区分大小写的。

            知识星球
            
    
    
        
        扫码加入星球
        查看更多优质内容
    
    https://wx.zsxq.com/mweb/views/joingroup/join_group.html?group_id=15522885221412
从跟上理解 MySQL,彻底掌握MySQL的字符集和比较规则
https://clxhxhhr.top/posts/2596/
作者
clxstart
发布于
2026-09-19
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。