目 录CONTENT

文章目录

乱码、字符集、比较规则

FatFish1
2025-04-24 / 0 评论 / 0 点赞 / 18 阅读 / 0 字 / 正在检测是否收录...

字符集

字符集的概念

计算机是通过二进制存储数据的,但是MySQL需要存字符串等,因此就要用字符集

字符集就是可编码的字符串范围+字符串如何映射到二进制数据(如何编码和解码)

常见字符集

一些重要的字符集包括:

  • ASCII字符集:只收录了128个字符,包括空格、标点、数字、大小写字面、一些不可见字符。由于只有128个,因此1个字节足够用了,比如'L'编码为01001100(0x4C,十进制为76)

  • ISO 8859-1(latin1):收录256个字符,除了ASCII,还收录了128个西欧字母。256也可以1个字节编码。

  • GB2312:收录了6763个汉字,以及其他字母,还兼容ASCII,这个字符集数量很大,因此1个字节不够用了,它会判断字符是否在ASCII中,如果在用1个字节,否则用2个字节,这叫变长编码。例如'爱u'表示为0xCED275。

    • 这就产生一个问题:怎么判断GB2312的1个字节是ASCII字符还是其他字符的一部分?因为ASCII的编码一定是0~127,即高位一定是0,GB2312的其他字符只占据128~255,即高位一定是1

  • GBK:扩充的GB2312,编码方式兼容GB2312,也是1个字符用1~2字节

  • utf8:最常用,收录全世界所有字符,兼容ASCII,也是变长编码,编码1个字符需要1~4个字节

    • utf16、utf32与utf8差不多,都是unicode编码方案,utf16固定用2个或4个字节编码一个字符,utf32固定用4个字节,因此使用utf8在MySQL中的好处是节省空间

乱码产生的原理

不同字符集的字符范围和编码类型不一样,但最终都是通过二进制存储

如果一组二进制数据在解码时选错了字符集,就有可能解码到意想不到的字符上,产生乱码

MySQL中的字符集

因为常用数据大部分1~3字节就够了,因此MySQL对utf8做了简化,从而降低存储空间:

  • utf8mb3:阉割版utf8字符集,只使用1~3字节表示字符,最常用,简称utf8

  • utf8b4:正宗的utf8字符集,使用1~4字节表示字符,有存储emoji的需求,必须用这种

MySQL可以使用如下命令查看字符集:

SHOW CHARSET [LIKE XXX]

查到的结果包括如下几个维度:

  • Charset:字符集

  • Description:描述

  • Default collation:该字符集默认的排序规则

  • Maxlen:该字符集描述一个字符需要的最大字节数

其中一些常用字符集的Maxlen比较有用:

字符集

Maxlen

ascii

1

latin1

1

gb2312

2

gbk

2

utf8

3

utf8mb4

4

比较规则

MySQL中的比较规则

MySQL要用于查询和比较,查到的结果必然就会有一定顺序,比较规则就是为了解决这个事情

可以使用如下命令查看比较规则:

SHOW COLLATION [LIKE xxx]

可以看到比较规则命名是比较整齐的,例如utf8_general_ci、utf8_bin、utf8_spanish_ci……

一般是2~3段式命名,含义如下:

  • 位置1:字符集名称,例如utf8,表示这个比较规则是给哪种字符集使用的

  • 位置2:语言,表示该比较规则是以哪种语言比较,例如上面的spanish是西班牙语,而general是指的通用规则,而utf8_bin是基于二进制比较的,因此没有语言这一说

  • 位置3:简述比较规则,一般是一些标志词,包括:

    • ai:accent insensitive,表示不区分重音

    • as:accent sensitive,区分重音

    • ci:case insensitive,不区分大小写

    • cs:case sensitive,区分大小写

    • bin:binary,以二进制方式比较

因此utf8_general_ci是一种不区分大小写比较的通用规则

比如按照插入数据存入了a、b、A、B、我五个字符

按照utf8_general_ci查询结果是a、b、A、B、我

按照gbk_bin查询结果则是A、B、a、b、我,因为转成gbk字符集的二进制结果,A=65,B=66,a=97,b=98,我=25105

想要知道每种字符集的默认比较规则,就在前面SHOW CHARSET [LIKE XXX] 展示的Default collation列中

比较规则的级别

四个级别:服务器级别、数据库级别、表级别、列级别

列级别不指定,去找所在表的级别;表级别不指定,去找所在库的级别;库级别不指定,去找所在服务器级别;服务器级别也不指定,取字符集的默认比较规则

服务器级别

通过两个系统变量可以查询和设置:

# 查询服务器级别的字符集
SHOW VARIABLES LIKE 'character_set_server';

# 查询服务器级别的比较规则
SHOW VARIABLES LIKE 'collation_server';

可以通过启动选项修改这两个参数:

[server]
character_set_server=gbk
collation_server=gbk_chinese_ci

数据库级别

create database语句和alter database语句中可以通过指定character set和collate关键词指定或修改,例如:

CREATE DATABASE charset_demo_db
CHARACTER SET gb2312
COLLATE gb2312_chinese_ci;

也可以使用SHOW语法查看当前库的配置,但是要先使用USE进入当前库

USE charset_demo_db;

# 查看数据库字符集
SHOW VARIABLES LIKE 'character_set_database';

# 查看数据库比较规则
SHOW VARIABLES LIKE 'collation_database';

character_set_database 和 collation_database 这两个系统变量是只读的,我们不能通过修改这两个变量的值 而改变当前数据库的字符集和比较规则

表级别

与库级别差不多,创建语法:

CREATE TABLE t(
	VARCHAR(10)
) CHARACTER SET utf8 COLLATE utf8_general_ci;

列级别

与表级别也差不多:

CREATE TABLE 表名(
 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称],
 其他列...
);

当表和列都设置了字符集,列显然会优先取本列的

修改字符集和比较规则

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

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

MySQL客户端与服务器端通信时的字符集转换

MySQL有三个配置项:

  • character_set_client:服务器解码请求时使用的字符集

  • character_set_connection:服务器处理请求时会把请求字符串从 character_set_client 转为 character_set_connection

  • character_set_results:服务器向客户端返回数据时使用的字符集

简单来说,就是:

  • character_set_client对应客户端所在操作系统的字符集,因为它是用来解码客户端发来请求的,肯定经过客户端编码

  • character_set_connection是服务器端使用的默认字符集,一般与表/列字符集一致,否则查询时还要再转一次

  • character_set_results是服务器端返回结果时编码的字符集,因此也要跟客户端操作系统字符集一致,否则编码的结果客户端解不出来

根据这个逻辑,大致可以分析出来,一次请求需要3~4次字符集转换:

  • 服务器解码客户端的二进制码,按照character_set_client转换一次

  • 服务器把请求按自己的默认字符集转码一次,按照character_set_connection转换

  • 服务器在查询时按照表/列字符集转换一次,将character_set_connection转成目标字符集(不一定发生)

  • 服务器返回时,按照character_set_results转换一次,给客户端解码用

因此尽量把这三个变量设置成与客户端和表一致,大部分情况都是使用utf8的就可以

0

评论区