MySQL排序踩坑:为什么"10"比"2"小?

14 阅读4分钟

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转数值(最常用)

SELECT * FROM your_table
ORDER BY CAST(your_column AS UNSIGNED);

UNSIGNED表示转为无符号整数。如果有小数用DECIMAL

ORDER BY CAST(your_column AS DECIMAL(10,2));

优点:简单直接 缺点:每次查询都要转换,数据量大的时候有性能开销

方案二:LPAD零填充(纯字符串方案)

如果不想改类型,又想让字符串按数值顺序排,可以用LPAD补零:

SELECT * FROM your_table
ORDER BY LPAD(your_column, 10, '0');

原理:把所有值补成相同长度,"2"变成"0000000002","10"变成"0000000010",这样字典序和数值序就一致了。

适用场景:数据长度固定或可预估的时候。如果最长有100位,这招就不好使了。

方案三:+0隐式转换(偷懒写法)

SELECT * FROM your_table
ORDER BY your_column + 0;

MySQL会自动把字符串转为数值。和CAST效果一样,但写法更简洁。

注意:如果列里有非数字字符(比如"abc"),会被转成0,可能产生意外结果。

方案四:FIELD指定顺序

如果排序规则不是按大小,而是有固定的业务顺序:

SELECT * FROM your_table
ORDER BY FIELD(your_column, '1', '3', '2', '10');

适用场景:状态排序(比如"待处理,处理中,已完成")、优先级排序。

方案五:创建自定义函数(复用)

如果这个排序逻辑在多处使用,可以封装成函数:

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);

方案六(最佳实践):从源头解决

把字段类型改成数值类型。

ALTER TABLE your_table MODIFY COLUMN your_column INT UNSIGNED;

这才是最正确的做法。如果设计之初就知道这个字段存的是数字,就应该用数值类型。VARCHAR存数字本身就是一个设计问题。

但现实中经常遇到历史数据不能随便改表结构的情况,这时候前面的方案就派上用场了。

性能对比

我用一张10万行的表简单测了一下:

方案耗时说明
CAST~120ms每行做类型转换
LPAD~150ms字符串补零+比较
+0隐式~120ms和CAST差不多
改为INT类型~15ms快了将近10倍

数据量越大差距越明显。所以如果能改表结构,优先改表结构

怎么避免踩这个坑

  1. 建表的时候就选对类型。存数字就用INT/BIGINT/DECIMAL,别用VARCHAR
  2. 如果字段确实需要存字符串(比如编码"V001""V002"),排序时记得用CAST
  3. Code Review的时候注意:ORDER BY的字段类型是什么?有没有可能出问题?
  4. 接口联调的时候验证排序:不要只看一两页数据,翻到有两位数、三位数的地方看看顺序对不对

总结

场景推荐方案
能改表结构改成INT类型(最优)
不能改表,偶尔查询CAST或+0
字符串补零排序LPAD
固定业务顺序FIELD
多处复用自定义函数

这个问题很基础,但恰恰是这种基础问题最容易在忙起来的时候忽略。写出来给大家提个醒:建表选对类型,能省后面很多事。


你有没有遇到过类似的"弱智bug"?欢迎评论区分享 👋