1. 新建数据库时的字符集与排序规则,到底该怎么选
很多人在安装完 MySQL 之后,第一步就是打开命令行或者 Navicat,敲一句CREATE DATABASE,但到了选择字符集那一栏,就开始犯难了。utf8mb4 和 utf8mb3 到底差在哪?utf8mb4_general_ci、utf8mb4_unicode_ci、utf8mb4_0900_ai_ci这一堆排序规则,看着都差不多,选错了会有什么后果?
先说结论:2024 年新建 MySQL 数据库,除非你有特殊的历史兼容需求,否则字符集选utf8mb4,排序规则在 MySQL 8.0 里默认的utf8mb4_0900_ai_ci可以直接用,在 5.7 里选utf8mb4_general_ci也问题不大。但这个结论背后有很多细节值得聊,因为字符集选错,前期可能看不出问题,等数据量上来、业务跑起来,再想改就麻烦大了。
这篇文章不是给你背文档,而是从实际建库的角度,把字符集、排序规则这些概念拆开揉碎,讲清楚它们是怎么影响你的数据存储、查询排序、索引命中的,再给你一套可以直接照着抄的建库方案。
适合谁看?刚入门 MySQL 的新手、正在做数据库课程设计的同学、从 Oracle 或 SQL Server 转过来的老开发,以及被线上乱码问题折磨过的运维朋友。看完这篇文章,你不仅知道怎么选,还能知道为什么这么选。
2. 先把概念搞清楚:字符集和排序规则不是一回事
2.1 字符集决定你能存什么,排序规则决定你怎么查
我见过不少同学把字符集和排序规则混为一谈,其实两者职责完全不同。
字符集(Character Set)决定了一个数据库能存储哪些字符。它是一张映射表,把人类能看懂的字符映射成计算机能存能算的二进制数据。比如 ASCII 字符集,只能存英文字母、数字和少量符号,一个字符占 1 个字节;而 GBK 能存中文,一个汉字占 2 个字节;utf8mb4 能存世界上几乎所有语言的字符,包括中文、日文、韩文、阿拉伯文,甚至 emoji 表情符号。
排序规则(Collation)则是在字符集的基础上,规定了字符之间如何比较和排序。也就是说,字符集决定"能存哪些字符",排序规则决定"这些字符谁大谁小、谁的优先级高、查的时候怎么匹配"。同一个字符集下,可以搭配多个不同的排序规则,它们对大小写是否敏感、对中文拼音排序是否友好、对特殊字符的权重处理方式,都会有差异。
我用一个生活化的类比来解释:字符集就像一套完整的砖块,砖块的种类决定了你能盖什么样的房子;排序规则就像你摆放砖块的顺序规则,同样一批砖,按大小排、按颜色排、按重量排,结果完全不一样。
2.2 utf8mb3 和 utf8mb4:一字之差,天壤之别
在 MySQL 里,utf8这个字符集名字本身就有一个历史包袱问题。在 MySQL 8.0 之前,utf8是utf8mb3的别名,它最多支持 3 个字节来存储一个字符。这意味着一个字符最多只能编码到 Unicode 的基本多语言平面(BMP),涵盖范围是绝大多数常用字符,中文、英文、日文假名、韩文谚文都在里面。
但问题是:emoji 表情不在 BMP 里面。emoji 需要 4 个字节才能存储。如果你建库的时候用了utf8(即 utf8mb3),往里插入一个 😀,立刻报错Incorrect string value,这就是很多人第一次遇到乱码和插入失败的常见原因之一。
utf8mb4则是完整的 UTF-8 编码,一个字符最多占 4 个字节,可以覆盖 Unicode 全部字符集。MySQL 官方从 8.0 开始,已经把默认字符集从utf8mb4之前的latin1改成了utf8mb4,等于官方也默认"新的数据库就应该用 utf8mb4"。
所以通用原则是:任何新建的数据库,不要再用utf8或utf8mb3,一律utf8mb4,除非你有极其特殊的存量业务限制。
2.3 排序规则里那些后缀是什么意思
看到一个排序规则名字,比如utf8mb4_0900_ai_ci,很多人一头雾水。其实拆开看并不复杂:
utf8mb4:这个排序规则基于的字符集0900:指的是 Unicode 排序算法版本号,9.0.0 版本。5.7 及之前的版本没有这个标识,因为当时用的是旧版权重ai:accent insensitive,表示对重音符号不敏感ci:case insensitive,表示对大小写不敏感
还有一个常见的bin后缀,比如utf8mb4_bin,表示二进制比较,直接按字符的二进制编码来比,区分大小写,不做任何语言层面的归一化。
理解这些后缀之后,你就能自己读懂 MySQL 里几十种排序规则的含义了。_general_ci是早期 MySQL 实现的一套简单比较逻辑,速度快但不够精确;_unicode_ci是实现了 Unicode 排序算法(UCA)的版本,比较精确但旧版(5.7 前的 unicode_ci)速度稍慢一些;8.0 里的_0900_ai_ci则是基于新版 UCA 9.0.0 的实现,做到了既快又准。
3. MySQL 8.0 和 5.7 默认值变化,别吃了旧习惯的亏
3.1 版本差异直接影响你的选择策略
很多人在 5.7 上用了很多年,习惯性地把CREATE DATABASE语句里的默认字符集改掉,然后直接用到 8.0 上。大部分情况下没问题,但默认排序规则的变化值得注意。
MySQL 8.0 的默认字符集是utf8mb4,默认排序规则是utf8mb4_0900_ai_ci。而 MySQL 5.7 的默认字符集是latin1,如果手动指定utf8mb4,默认排序规则是utf8mb4_general_ci。
两个版本都是 utf8mb4 字符集,但排序规则不同,会导致什么问题?最典型的是索引失效。如果你有一个表在 5.7 上用utf8mb4_general_ci建好了,然后迁移到 8.0,某些查询如果你在 SQL 里不写排序规则,MySQL 会使用表的默认设置。两边排序规则不一致,关联查询时可能出现Illegal mix of collations的错误,直接报错,或者导致无法使用索引,查询性能骤降。
所以,跨版本迁移时不要只看字符集,排序规则也必须统一检查。
3.2 8.0 的 utf8mb4_0900_ai_ci 到底好在哪
如果说 5.7 年代你必须在general_ci和unicode_ci之间做取舍,那么 8.0 的0900_ai_ci基本上终结了这个选择题。
_0900_ai_ci基于 Unicode 9.0.0 标准的校对算法,在比较和排序的准确性上,比general_ci高了一个台阶。最直观的例子是:general_ci在比较英文字符时,有一些历史遗留的"不规范"行为,比如它会把a、à、á视为同一级别的等价,但某些边缘字符的处理与 Unicode 标准不一致。而0900_ai_ci严格遵循 Unicode Collation Algorithm,处理多语言文本时更加符合预期。
性能上,8.0 对 UCA 实现做了大量优化,0900_ai_ci的排序和比较速度比 5.7 时代的utf8mb4_unicode_ci更快,接近甚至超过general_ci。所以"精确的比较慢"这个旧印象已经过时了。
3.3 有个大坑:0900_ai_ci 对中文拼音排序并不友好
虽然0900_ai_ci在 Unicode 标准上很"正确",但如果你在中国做业务,有一个实际情况要想清楚:你的中文排序是按什么规则排?
utf8mb4_0900_ai_ci是中性的 Unicode 排序规则,它不专门针对中文拼音做优化。如果你对一列中文数据用ORDER BY name,得到的顺序是按 Unicode 编码点排序,不是按拼音首字母排。这意味着"阿里巴巴"不一定会排到"百度"前面,大概率是按汉字的 Unicode 编码顺序来的。
如果你的业务明确要求中文按拼音排序,就不能只依赖数据库默认排序规则,要么在应用层用拼音字段单独处理,要么考虑其他方案。这个跟排序规则选哪个没有绝对关系,0900_ai_ci不是不好,而是它的设计目标本来就不是中文专属排序。
4. 新建数据库的实操指南:命令行和图形化工具双方案
4.1 命令行建库:这条 SQL 直接抄
如果你习惯命令行操作,新建数据库的完整 SQL 语句如下:
CREATE DATABASE `your_db_name` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_0900_ai_ci;这是 MySQL 8.0 上最推荐的写法。如果你用的是 MySQL 5.7,utf8mb4_0900_ai_ci这个排序规则并不存在,需要换成utf8mb4_general_ci:
CREATE DATABASE `your_db_name` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;<注意> MySQL 5.7 不能使用utf8mb4_0900_ai_ci,8.0 之前的版本没有这个规则。如果你在 5.7 里执行会直接报错,这也是很多人从网上复制建库语句到旧版本执行失败的主要原因。 </注意>
建完数据库之后,建议顺手验证一下:
SHOW CREATE DATABASE `your_db_name`;这条命令会显示该数据库的实际字符集和排序规则,确保跟你的预期一致,不会因为全局配置或 session 配置的干扰而变成别的字符集。
4.2 Navicat 图形化建库:点几下就搞定
用 Navicat 之类的图形化工具更简单。连接数据库后,右键点击"连接"下的数据库区域,选择"新建数据库",弹出窗口里填写数据库名,然后重点看"字符集"和"排序规则"两个下拉框。
字符集选择utf8mb4,排序规则在 MySQL 8.0 上会自动带出utf8mb4_0900_ai_ci,保持默认即可;在 MySQL 5.7 上会自动带出utf8mb4_general_ci,也保持默认即可。如果你想让中文按拼音排序且不区分重音,可以选择utf8mb4_zh_0900_as_cs(MySQL 8.0 提供的中文排序规则之一),但要注意它区分大小写。
很多初学者容易踩的坑是:数据库这一层的字符集选好了,但建表时不指定,默认继承数据库的;可如果你建表时手动指定成了别的字符集,那数据库层面的设置就白做了。表层面的字符集优先级高于数据库层面,所以建表时务必也检查一下。
4.3 连接层的编码:建库对了只是一半
数据库建对了字符集,但你的连接层编码不对,照样会出现中文乱码或 emoji 存不进去的情况。常见的表现是:数据库设置明明是 utf8mb4,但通过命令行查询,返回的中文是乱码;或者程序写入时,中文显示正常但特殊字符变成问号。
我给你的排查和解决路径是固定的:
连接字符串里必须指定字符集参数。JDBC 连接串加上characterEncoding=utf8和useUnicode=true;PHP 的 mysqli 连接加上SET NAMES utf8mb4;Python 的 pymysql 连接参数里加上charset='utf8mb4'。
连接建立成功后,可以执行一句:
SHOW VARIABLES LIKE 'character_set_connection';确认连接层字符集是utf8mb4而不是latin1。很多乱码问题根源不在建库,而在连接层默认字符集不对,这个我之前排查线上问题时遇到过很多次了。
5. 不同业务场景下,排序规则的选择策略
5.1 通用业务系统:就用默认,别折腾
如果你做的是一套常规的 Web 系统、管理后台、内容管理系统,用户主要用简体中文和英文,没有特殊的排序、搜索需求,我的建议非常简单:MySQL 8.0 直接用utf8mb4_0900_ai_ci,MySQL 5.7 直接用utf8mb4_general_ci。
理由一是官方默认值就是经过充分考虑和测试的,踩坑概率最低;理由二是现在很多 ORM 框架和数据库迁移工具,默认配置都按官方默认值来,你手动改成其他排序规则,反而可能在工具链里产生兼容问题。
比如有些团队用 Flyway 管理数据库版本,脚本里写死了排序规则,换数据库实例时发现排序规则不存在,导致迁移失败。这种问题属于"自己给自己找麻烦",没必要。
5.2 多语言业务:注意大小写和重音匹配策略
如果你的业务涉及多语言,比如跨境电商、国际化社交产品,那0900_ai_ci的优势能发挥出来。ai(accent insensitive)意味着用户在搜索 "cafe" 时可以匹配到 "café",ci(case insensitive)意味着 "Apple" 和 "apple" 视为相同。
这种"宽容"的匹配规则对搜索体验来说通常是好事,但它也会带来一个副作用:如果你需要做精确去重,比如用户名不区分大小写,那用_ci排序规则建唯一索引时会发现 "Admin" 和 "admin" 会冲突,这是符合预期的。而如果业务要求用户名严格区分大小写,那就该选utf8mb4_bin或utf8mb4_0900_bin这类二进制排序规则。
5.3 需要二进制精确匹配的场景
有些业务场景对字符比较的严格性要求很高。典型的如存储系统路径、JWT token、哈希值、编码唯一标识。这些值本质上是"大小写敏感"的字符串,如果用_ci规则去建唯一索引,一旦存在 "AbC" 和 "abc" 两个不同的 token,数据库直接拒绝插入第二个。
这种场景,就应该用utf8mb4_bin(5.7)或utf8mb4_0900_bin(8.0)。二进制排序规则对两个字符串的每一个字节直接比较,不存在大小写归一化,只要字节不同就算不同。要注意的是,选择_bin规则后,ORDER BY的结果会按字节序排列,这通常不是你想要的"人性化"排序,所以日常业务查询如果需要按名称排序,建议在应用层做,或者额外加一个拼音/笔画排序字段。
5.4 中文排序需求:拼音排序到底怎么处理
说句实在话,MySQL 对中文拼音排序的支持一直不算好。MySQL 5.7 的utf8mb4_general_ci和 8.0 的utf8mb4_0900_ai_ci都不能完美解决中文按拼音排序的问题。MySQL 8.0 引入了utf8mb4_zh_0900_as_cs这个中文专属排序规则,实测可以按中文拼音排序,但它同时满足as(accent sensitive,重音敏感)和cs(case sensitive,大小写敏感),也就是说它区分大小写,这在某些业务中不符合要求。
如果你的业务确实需要按中文拼音排序,我的实操建议是:不要在数据库层面硬扛,而是在用户表中单独维护一个pinyin或sort_key字段,插入记录时用 Java 的 pinyin4j、Python 的 pypinyin 这类库生成拼音索引,然后按这个字段排序。这样保证了排序的确定性和灵活性,不管是拼音、笔画还是自定义权重,都能实现。
6. 已经建好库了,发现字符集不对怎么补救
6.1 数据库层面直接改,但要分批操作
如果你的库是刚建的,里面没什么数据,直接执行:
ALTER DATABASE `your_db_name` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_0900_ai_ci;这个命令只改数据库的默认属性,不会自动修改已有表的字符集。如果你的库里面已经有表了,每个表需要单独改:
ALTER TABLE `your_table_name` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;CONVERT TO这个命令会直接把表中已有的数据也转成新的字符集,如果数据量大、文本字段多,会锁表,可能影响线上业务,一定不要在生产环境直接执行,要先在测试环境验证,或者用在线 DDL 工具。
6.2 数据已经乱码了,怎么救回来
最麻烦的情况是:数据库建立了很久,数据已经以错误的字符集存进去了,中文显示成???或者一堆乱七八糟的符号。
先说一个残酷的现实:如果数据在存储时因为字符集不对已经变成了?,那是无法恢复的,因为原始字节已经丢了。但如果只是显示层面的混乱(比如客户端字符集和数据库不一致导致的"错位显示"),还有机会通过重新转换恢复。
我踩过一个典型的坑:表原来是latin1存储,但业务写入时实际写的是 UTF-8 字节。此时 Navicat 打开中文显示是乱码,但如果我执行ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,会基于latin1的字节做一次新映射,反而让乱码更严重。正确的操作是先用latin1导出数据,再用utf8mb4导入,或者用HEX()函数确认原字节,然后手动转码。
遇到这种情况,不要着急,先把SELECT HEX(column)出来,看原始字节是否符合正常 UTF-8 编码的规则,再决定转码方案。
6.3 改完字符集后,别忘了重建索引
很多人改完表字符集后,发现查询速度变慢了,以为是字符集本身的问题,其实真正的原因是原有的索引没有重建。字符集改变后,索引的排序规则也需要跟着变,MySQL 某些版本下旧的索引可能没有自动适配。
我建议你在完成ALTER TABLE ... CONVERT TO之后,务必检查一下:
SHOW INDEX FROM `your_table_name`;必要时删除旧的索引并重新创建,确保索引的 collation 和当前表一致。否则排查半天性能问题,结果发现是索引没重建,那就太冤了。
7. 常见问题速查与避坑指南
先说一个高频问题:为什么我建表时明明写了 utf8mb4,但表实际是 latin1?
这种情况通常是 MySQL 配置文件里的character_set_server被设置成了别的值,或者建表语句中某个字段级别写了CHARACTER SET latin1覆盖了表级别的设置。字段级 > 表级 > 数据库级 > 服务器级,优先级从高到低是这样的。你建表时如果字段级别的字符集指定了别的,表级设置不会生效。解决方式是检查建表语句,确保没有字段级字符集覆盖。
再来一个高频问题:可以只改一列的字符集吗?
可以,但非必要不建议。ALTER TABLE只针对单个字段改字符集,会产生行级锁或表级锁,数据量大时影响较大。而且字段级字符集和表级字符集不一致,会埋下隐患:查询时如果关联字段的 collation 不一致,会出现Illegal mix of collations报错。我的建议是保持整表统一,不要搞出"混血"的表结构。
关于排序规则和索引的搭配,还有一个很重要的点,我写了这么多次collation相关的排查,最后总结成一个速查表给你:
| 场景 | MySQL 8.0 推荐 | MySQL 5.7 推荐 |
|---|---|---|
| 常规业务系统(中文+英文) | utf8mb4_0900_ai_ci | utf8mb4_general_ci |
| 多语言搜索、不区分重音 | utf8mb4_0900_ai_ci | utf8mb4_unicode_ci |
| 用户名、token、大小写敏感 | utf8mb4_0900_bin | utf8mb4_bin |
| 中文拼音排序 | utf8mb4_zh_0900_as_cs或应用层处理 | 应用层处理 |
最后一个提醒,很多人在建库的时候容易忽略列级别的字符集。表建好了,字符集也对了,但字段级别如果单独指定了,还是会在查询和排序时出问题。最稳妥的检查方式是:
SHOW FULL COLUMNS FROM `your_table_name`;查看Collation一列,确保所有文本字段都跟表保持一致。
8. 建库前想清楚这三步,能少走很多弯路
新建一个数据库,字符集和排序规则看起来只是初始化的一个步骤,但它的影响会跟随这个库的整个生命周期。我个人的体感是,很多人因为前期没想清楚,后续在数据迁移、跨表关联、乱码修复上花的时间,远比当初多花五分钟搞清楚要耗得多。
我平时调研一个数据库选型,基本遵循这三步:
第一步,确认 MySQL 大版本,因为 5.7 和 8.0 可用的排序规则完全不同,这一步直接决定了你能选什么。
第二步,评估业务数据的语言范围,是不是只要中文和英文,还是有多语言需求;有没有 emoji 存储需求;是否需要大小写敏感的比较。
第三步,确认排序需求,是默认的编码序就可以,还是需要按拼音、笔画、重音等特定规则排序。
这三步想清楚之后,再打开命令行或者 Navicat 建库,你心里就有底了,不会再对着下拉框发愁,也不会被网上各路说法带偏。