news 2026/9/1 20:18:29

MySQL按中文排序:ORDER BY遇到中文乱序怎么办?5种方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL按中文排序:ORDER BY遇到中文乱序怎么办?5种方案

做后台开发的同学应该都碰到过这个场景:页面上有个下拉列表或者表格,需要按中文姓名、城市名排序显示,结果一查出来,张三排到李四前面还是后面完全看运气,搞得产品经理天天追着你问"这个排序怎么是乱的"。

MySQL默认用的是latin1或者utf8字符集,排序的时候按照字符编码值来比大小。中文编码本身不是按拼音顺序排列的,所以直接 ORDER BY name 排出来的结果基本没法看。

今天把MySQL中文排序的几种方案全讲一遍,每种都配上可以直接跑的SQL,看完直接用。


一、先看看问题长什么样

建一张测试表,塞几条数据进去:

CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), city VARCHAR(50), score INT ) CHARSET=utf8mb4; INSERT INTO student (name, city, score) VALUES ('张三', '北京', 85), ('李四', '上海', 92), ('王五', '广州', 78), ('赵六', '深圳', 88), ('陈七', '杭州', 95), ('刘八', '成都', 70), ('黄九', '武汉', 83), ('周十', '南京', 90);

先用默认方式排序看看:

SELECT name FROM student ORDER BY name;

结果大概率是这样:

刘八 周十 张三 李四 王五 赵六 黄九 陈七

你说这顺序有啥规律?没有。按编码值排的,和人脑期望的拼音顺序完全对不上。


二、方案一:CONVERT函数转GBK排序(最常用)

这是用得最多的一种方式,原理很简单:GBK编码本身就是按拼音顺序编排的,把UTF8的中文字段转成GBK再排序,就能得到拼音顺序的结果。

SELECT name, city FROM student ORDER BY CONVERT(name USING gbk);

输出:

陈七 黄九 李四 刘八 王五 张三 赵六 周十

Chen、Huang、Li、Liu、Wang、Zhang、Zhao、Zhou,拼音顺序,没问题。

带升降序的写法:

-- 拼音升序 SELECT name, city FROM student ORDER BY CONVERT(name USING gbk) ASC; -- 拼音降序 SELECT name, city FROM student ORDER BY CONVERT(name USING gbk) DESC;

多字段混合排序:

-- 先按城市拼音排序,同一个城市再按分数降序 SELECT name, city, score FROM student ORDER BY CONVERT(city USING gbk), score DESC;

注意:CONVERT方式对多音字无能为力。比如"重庆"的"重"会按zhong排序而不是chong,"银行"的"行"会按xing而不是hang。MySQL不知道你这个词到底是哪个读音,它只认字形编码。如果业务对多音字有严格要求,得在表里加一个拼音字段。


三、方案二:建表时指定GBK字符集

如果一张表的中文字段经常需要按拼音排序,建表的时候直接把字符集设成GBK,以后排序就不用每次都CONVERT了。

CREATE TABLE student_gbk ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) CHARSET=gbk, city VARCHAR(50) CHARSET=gbk, score INT ) CHARSET=utf8mb4;

然后正常排序就行:

SELECT name FROM student_gbk ORDER BY name;

直接就是拼音顺序。不过这种做法有个明显的限制:整张表混用字符集,后续维护容易出问题,而且GBK字符集支持的字符范围比utf8mb4小很多,生僻字可能存不进去。

更推荐的做法是只给需要排序的字段指定GBK:

CREATE TABLE student2 ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) CHARSET=gbk, city VARCHAR(50) CHARSET=gbk, score INT, remark TEXT -- 这个字段存备注,用utf8mb4,支持emoji和生僻字 ) CHARSET=utf8mb4;

四、方案三:加拼音辅助字段(多音字场景推荐)

前面说了,CONVERT转GBK解决不了多音字问题。如果你的业务场景里多音字很常见(比如地名、人名),最靠谱的办法是在表里加一列存拼音,插入数据的时候就把拼音生成好。

CREATE TABLE contact ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), name_pinyin VARCHAR(100), phone VARCHAR(20) ) CHARSET=utf8mb4;

插入数据时把拼音带上:

INSERT INTO contact (name, name_pinyin, phone) VALUES ('张三', 'zhangsan', '13800000001'), ('重庆', 'chongqing', '13800000002'), ('李白', 'libai', '13800000003'), ('银行', 'yinhang', '13800000004');

排序直接按拼音字段来:

SELECT name, phone FROM contact ORDER BY name_pinyin;

输出:

重庆 13800000002 李白 13800000003 张三 13800000001 银行 13800000004

Chongqing、Libai、Zhangsan、Yinhang,多音字的读音完全由你控制。

Java代码自动生成拼音的方案:

实际项目中,手动填拼音太蠢了,一般用pinyin4j这类库在代码层自动转换:

<!-- Maven依赖 --> <dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.1</version> </dependency>
import net.sourceforge.pinyin4j.PinyinHelper; public class PinyinUtil { public static String toPinyin(String chinese) { if (chinese == null || chinese.isEmpty()) { return ""; } StringBuilder sb = new StringBuilder(); for (char c : chinese.toCharArray()) { if (c >= 128) { String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c); if (pinyinArray != null && pinyinArray.length > 0) { sb.append(pinyinArray[0]); } } else { sb.append(c); } } return sb.toString().toLowerCase(); } }

但pinyin4j默认取的是第一个读音(多音字的"最常见读音"),不一定准确。如果业务对拼音精度要求高,要么自己维护一个多音字词典,要么用更专业的分词库比如HanLP。


五、方案四:修改列的COLLATE排序规则

MySQL的排序行为由COLLATE(校对规则)控制。utf8mb4默认的校对规则是 utf8mb4_general_ci,这个规则对中文排序不太友好。可以改成 utf8mb4_unicode_ci 或者 utf8mb4_zh_0900_as_cs(MySQL 8.0+)。

会话级别临时修改:

-- 整个会话的排序规则改为支持中文的 SET NAMES utf8mb4 COLLATE utf8mb4_zh_0900_as_cs; SELECT name FROM student ORDER BY name;

查询时指定COLLATE:

SELECT name FROM student ORDER BY name COLLATE utf8mb4_zh_0900_as_cs;

修改表字段的COLLATE:

ALTER TABLE student MODIFY COLUMN name VARCHAR(50) COLLATE utf8mb4_zh_0900_as_cs;

改完之后,直接 ORDER BY name 就能得到按中文拼音排序的结果。

utf8mb4_zh_0900_as_cs 是MySQL 8.0新引入的中文排序规则,基于Unicode Collation Algorithm(UCA),对中文的排序支持比老的 utf8mb4_unicode_ci 好不少。如果你的MySQL版本是8.0以上,优先考虑这个方案。

几个中文相关COLLATE的对比:

COLLATE名称

MySQL版本

说明

utf8mb4_general_ci

5.5+

默认规则,中文排序不可用

utf8mb4_unicode_ci

5.5+

比general好一些,但中文拼音排序不够准确

utf8mb4_zh_0900_as_cs

8.0+

专为中文设计,拼音排序准确,区分大小写

utf8mb4_zh_0900_ai_ci

8.0+

同上,不区分大小写

gbk_chinese_ci

5.5+

GBK字符集的中文排序规则,按拼音排序


六、方案五:ORDER BY FIELD自定义排序

有些场景不是按拼音排,而是按业务自定义的顺序排。比如城市要按"北上广深"排在前面,其他城市按拼音排。这时候用FIELD函数。

SELECT name, city, score FROM student ORDER BY FIELD(city, '北京', '上海', '广州', '深圳'), CONVERT(city USING gbk);

输出:

张三 北京 85 李四 上海 92 王五 广州 78 赵六 深圳 88 陈七 杭州 95 刘八 成都 70 黄九 武汉 83 周十 南京 90

北京、上海、广州、深圳排在最前面(按FIELD指定的顺序),后面杭州、成都、武汉、南京按拼音排列。

FIELD函数的原理:FIELD(value, v1, v2, v3, ...) 返回value在列表中的位置,找不到返回0。所以FIELD的排序结果是:不在列表中的排最前(值为0),然后按列表顺序排。

如果你想不在列表中的排后面,加个DESC或者在FIELD外面套一层:

-- 不在列表中的城市排后面 SELECT name, city FROM student ORDER BY FIELD(city, '北京', '上海', '广州', '深圳') = 0, FIELD(city, '北京', '上海', '广州', '深圳');

FIELD(...) = 0 这一项让不在列表中的城市(返回true=1)排到后面,在列表中的(返回false=0)排前面。


七、实战场景完整示例

场景1:通讯录按姓名拼音分组显示

SELECT SUBSTRING(CONVERT(name USING gbk), 1, 1) AS first_char, GROUP_CONCAT(name ORDER BY CONVERT(name USING gbk) SEPARATOR '、') AS names FROM student GROUP BY SUBSTRING(CONVERT(name USING gbk), 1, 1) ORDER BY first_char;

模拟通讯录按首字母分组的效果。

场景2:省市联动选择框,省份按拼音排序

-- 省份表 CREATE TABLE province ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) ) CHARSET=utf8mb4; INSERT INTO province (name) VALUES ('北京市'), ('上海市'), ('广东省'), ('浙江省'), ('江苏省'), ('四川省'), ('湖北省'), ('湖南省'), ('河南省'), ('河北省'); -- 按拼音排序输出 SELECT id, name FROM province ORDER BY CONVERT(name USING gbk);

输出:北京市、广东省、湖北省、河南省、河北省、江苏省、上海市、四川省、浙江省、湖南省(拼音顺序)。

场景3:中文和英文混合排序

CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) ) CHARSET=utf8mb4; INSERT INTO product (name) VALUES ('iPhone 15'), ('华为Mate60'), ('小米14'), ('OPPO Find X7'), ('vivo X100'), ('三星S24'), ('荣耀Magic6'); -- 英文排在前面,中文排在后面,各自按规则排序 SELECT name FROM product ORDER BY CASE WHEN name REGEXP '^[a-zA-Z]' THEN 0 ELSE 1 END, CASE WHEN name REGEXP '^[a-zA-Z]' THEN name ELSE CONVERT(name USING gbk) END;

输出:

iPhone 15 OPPO Find X7 vivo X100 华为Mate60 荣耀Magic6 三星S24 小米14

场景4:大数据量下的性能考虑

CONVERT函数在排序时会导致MySQL无法使用索引,每次排序都是filesort。数据量小的时候无所谓,百万级以上就能感觉到慢了。

这时候有两个优化思路:

思路一:加存储拼音的索引列

ALTER TABLE student ADD COLUMN name_pinyin VARCHAR(100); UPDATE student SET name_pinyin = CONVERT(name USING gbk); ALTER TABLE student ADD INDEX idx_name_pinyin (name_pinyin);

查询直接按索引列排:

SELECT name FROM student ORDER BY name_pinyin;

思路二:触发器自动维护拼音列

DELIMITER // CREATE TRIGGER trg_student_insert BEFORE INSERT ON student FOR EACH ROW BEGIN SET NEW.name_pinyin = CONVERT(NEW.name USING gbk); END // CREATE TRIGGER trg_student_update BEFORE UPDATE ON student FOR EACH ROW BEGIN IF NEW.name <> OLD.name THEN SET NEW.name_pinyin = CONVERT(NEW.name USING gbk); END IF; END // DELIMITER ;

这样插入和更新的时候自动维护拼音列,查询时直接按索引列排序,性能和普通排序没有区别。


八、方案选择指南

到底用哪种方案?根据实际情况选:

场景

推荐方案

理由

数据量小(万级以下),临时排序

CONVERT转GBK

简单直接,一行SQL搞定

MySQL 8.0+,表结构可改

改COLLATE为utf8mb4_zh_0900_as_cs

一劳永逸,排序最准确

多音字多的业务(人名、地名)

加拼音辅助字段

完全可控,性能也好

大数据量,排序频繁

拼音列 + 索引

走索引,不触发filesort

自定义业务排序顺序

ORDER BY FIELD

灵活控制任意顺序


九、几个容易踩的坑

坑1:utf8和utf8mb4不是一回事。MySQL里的utf8最多只支持3字节字符,utf8mb4才支持完整的Unicode(包括emoji)。建表时统一用utf8mb4,排序也一样。

坑2:CONVERT在某些MySQL版本中可能报错。确保你的MySQL编译时包含了GBK字符集支持。可以用 SHOW CHARACTER SET LIKE 'gbk'; 检查。

坑3:排序结果在不同MySQL版本间可能不一致。尤其是 utf8mb4_unicode_ci,MySQL 5.7和8.0对中文的处理有差异。上线前在目标环境测试一遍。

坑4:排序和存储字符集不匹配。如果表的字符集是utf8mb4,但字段的COLLATE是 utf8mb4_general_ci,CONVERT转GBK排序的结果可能跟预期有出入。最稳妥的办法是统一字符集和COLLATE设置。

坑5:数据里有繁体字。GBK编码里包含繁体字,但繁体字的编码位置和简体字不在一起,排序结果会把简体和繁体分开。如果业务需要简繁混合排序,建议走拼音列方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 20:11:41

Sentinel实战:微服务限流、熔断与降级的核心原理与落地

先说结论&#xff1a;Sentinel 是面向微服务、分布式系统的流量治理组件&#xff0c;核心就三件事&#xff1a;限流、熔断降级、系统保护。微服务里真正让人头疼的不是功能开发&#xff0c;而是流量一上来、下游一慢、某个接口一抖动&#xff0c;整个链路跟着挂。很多人把“限流…

作者头像 李华
网站建设 2026/9/1 20:10:08

深入理解POSIX:编写跨平台Shell脚本的兼容性指南

各位读者朋友&#xff0c;大家好。之前在实际开发中&#xff0c;经常遇到这样一个场景&#xff1a;同一份 Shell 脚本&#xff0c;在一台 Ubuntu 服务器上运行得好好的&#xff0c;换到本机 macOS 终端里就报错&#xff0c;或者输出结果对不上。网上搜了一圈&#xff0c;答案零…

作者头像 李华
网站建设 2026/9/1 20:07:27

一个 Java 工程师的 28 天 Python 之旅:从“复制粘贴“到自己的作品

我是一个写了多年 Java 的后端工程师&#xff0c;动手能力不算强&#xff0c;学东西主要靠模仿和记忆。这篇文章记录我用 28 天学完 Python 全栈&#xff08;数据处理 → Web 接口 → 数据库 → 页面 → AI 模型&#xff09;的完整过程&#xff0c;包括第 20 天差点弃坑的迷茫、…

作者头像 李华
网站建设 2026/9/1 20:04:39

1.web记录

1.js数据类型基本数据类型&#xff1a;string boolean number undefined null symbol bigInt引用数据类型:Object(对象 数组 函数)2.怎么去判断数据类型方法一&#xff1a;typeof 不能判断 null、Array、Object方法二&#xff1a;instanceof 判断Object 不能判断 null、undef…

作者头像 李华