MySQL排序踩坑:为什么"10"比"2"小?
一个让我debug半小时的bug
前几天写了个接口,返回一个排序列表。前端说数据顺序不对,我一看还真是:
预期:1, 2, 3, 10, 20, 100
实际:1, 10, 100, 2, 20, 3
10排在2前面?这什么鬼?
后来查了半天才发现,这个字段在数据库里是VARCHAR类型,存的是字符串。MySQL按字符串排序的时候,是逐字符比较的,不是按数值大小。所以"10"的第一个字符是"1","2"的第一个字符是"2","1" < "2",于是"10"就排在了"2"前面。
字典序:1 < 10 < 100 < 2 < 20 < 3 ← MySQL实际排序 数值序:1 < 2 < 3 < 10 < 20 < 100 ← 我们想要的
这个问题其实很基础,但踩坑的时候如果没往类型上想,真的能debug很久。
解决方案
方案一:CAST转数值(最常用)
sql
SELECT * FROM your_table
ORDER BY CAST(your_column AS UNSIGNED);
UNSIGNED表示转为无符号整数。如果有小数用DECIMAL:
sql
ORDER BY CAST(your_column AS DECIMAL(10,2));
优点 :简单直接 缺点:每次查询都要转换,数据量大的时候有性能开销
方案二:LPAD零填充(纯字符串方案)
如果不想改类型,又想让字符串按数值顺序排,可以用LPAD补零:
sql
SELECT * FROM your_table
ORDER BY LPAD(your_column, 10, '0');
原理:把所有值补成相同长度,"2"变成"0000000002","10"变成"0000000010",这样字典序和数值序就一致了。
适用场景:数据长度固定或可预估的时候。如果最长有100位,这招就不好使了。
方案三:+0隐式转换(偷懒写法)
sql
SELECT * FROM your_table
ORDER BY your_column + 0;
MySQL会自动把字符串转为数值。和CAST效果一样,但写法更简洁。
注意:如果列里有非数字字符(比如"abc"),会被转成0,可能产生意外结果。
方案四:FIELD指定顺序
如果排序规则不是按大小,而是有固定的业务顺序:
sql
SELECT * FROM your_table
ORDER BY FIELD(your_column, '1', '3', '2', '10');
适用场景:状态排序(比如"待处理,处理中,已完成")、优先级排序。
方案五:创建自定义函数(复用)
如果这个排序逻辑在多处使用,可以封装成函数:
sql
DELIMITER //
CREATE FUNCTION sort_numeric(s VARCHAR(255)) RETURNS INT
BEGIN
RETURN CAST(SUBSTRING_INDEX(s, '.', 1) AS UNSIGNED);
END //
DELIMITER ;
-- 使用
SELECT * FROM your_table ORDER BY sort_numeric(your_column);
方案六(最佳实践):从源头解决
把字段类型改成数值类型。
sql
ALTER TABLE your_table MODIFY COLUMN your_column INT UNSIGNED;
这才是最正确的做法。如果设计之初就知道这个字段存的是数字,就应该用数值类型。VARCHAR存数字本身就是一个设计问题。
但现实中经常遇到历史数据不能随便改表结构的情况,这时候前面的方案就派上用场了。
性能对比
我用一张10万行的表简单测了一下:
| 方案 | 耗时 | 说明 |
|---|---|---|
| CAST | ~120ms | 每行做类型转换 |
| LPAD | ~150ms | 字符串补零+比较 |
| +0隐式 | ~120ms | 和CAST差不多 |
| 改为INT类型 | ~15ms | 快了将近10倍 |
数据量越大差距越明显。所以如果能改表结构,优先改表结构。
怎么避免踩这个坑
- 建表的时候就选对类型。存数字就用INT/BIGINT/DECIMAL,别用VARCHAR
- 如果字段确实需要存字符串(比如编码"V001""V002"),排序时记得用CAST
- Code Review的时候注意:ORDER BY的字段类型是什么?有没有可能出问题?
- 接口联调的时候验证排序:不要只看一两页数据,翻到有两位数、三位数的地方看看顺序对不对
总结
| 场景 | 推荐方案 |
|---|---|
| 能改表结构 | 改成INT类型(最优) |
| 不能改表,偶尔查询 | CAST或+0 |
| 字符串补零排序 | LPAD |
| 固定业务顺序 | FIELD |
| 多处复用 | 自定义函数 |
这个问题很基础,但恰恰是这种基础问题最容易在忙起来的时候忽略。写出来给大家提个醒:建表选对类型,能省后面很多事。
你有没有遇到过类似的"弱智bug"?欢迎评论区分享 👋