为什么阿里坚决抵制使用 ORDER BY RAND()?
摘要:如果你翻阅过《阿里巴巴 Java 开发手册》,在 MySQL 数据库规约中,一定见过这条醒目的“红线”:【强制】不得在 database 中使用 ORDER BY RAND() 进行随机排序。
MySQL专栏
如果你翻阅过《阿里巴巴 Java 开发手册》,在 MySQL 数据库规约中,一定见过这条醒目的“红线”:
【强制】不得在 database 中使用 ORDER BY RAND() 进行随机排序。
很多人第一反应是:“不就随机查几条数据吗?MySQL 既然提供了这个内置函数,为什么不让用?”
事实上,这可能是 MySQL 里最“坑爹”的内置函数之一。在数据量只有几百条时,它是省时省力的小甜甜;一旦数据量突破十万级,它立马变身吸干 CPU 的“牛夫人”,分分钟让你的数据库报警。
今天我们就来扒一扒,为什么这个函数是性能杀手,以及在海量数据下,我们该如何优雅且高性能地实现“随机推荐”功能。
案发现场:一条 SQL 引发的血案
那个让 DBA 暴跳如雷的 SQL 长这样:
-- 看起来人畜无害,实则剧毒无比
SELECT * FROM product ORDER BY RAND() LIMIT 3;
如果你的商品表只有几百条数据,怎么玩都行。但当数据量达到 几万、几十万甚至上百万 时,这条 SQL 就是一颗定时炸弹。
为什么它这么慢?
我在测试环境重现了一下,顺手敲了个 EXPLAIN。好家伙,Extra 字段里赫然写着:
Using temporary; Using filesort
这简直是 MySQL 性能杀手界的“卧龙凤雏”!
ORDER BY RAND() 的执行流程大致是这样的:
全表扫描:MySQL 需要为每一行数据生成一个随机值。
创建临时表:把查询列和对应的随机值塞进临时表(如果内存不够,还会用到磁盘临时表)。
全局排序:对临时表里的随机值进行排序。
取出前几条:这就好比你要从一袋米里随机挑 3 粒,却先把整袋米倒出来,给每粒米编个号,排个序,再挑前 3 个。
这不崩谁崩?
深入剖析:五种高性能替代方案
既然 ORDER BY RAND() 不能用,那怎么实现“随机推荐”?其实思路很简单:把“计算随机”的压力从 Database 转移到 Application(应用层),或者减少数据库的扫描行数。
方案一:应用层随机法(Application Shuffle)
适用场景:数据量不大(例如 < 10万),内存不值钱。
核心思想:既然数据库随机排序慢,那我把 ID 全拿出来,在 Java 代码里洗牌行不行?
代码实现
// 1. 查出所有商品ID(只查ID,速度飞快)
// SQL: SELECT id FROM product;
List<Integer> allProductIds = productMapper.selectAllIds();
// 2. 利用 Java 的 Collections 工具类进行洗牌
Collections.shuffle(allProductIds);
// 3. 截取前3个
List<Integer> randomIds = allProductIds.subList(0, 3);
// 4. 回表批量查询详情
// SQL: SELECT * FROM product WHERE id IN (..., ..., ...);
List<Product> results = productMapper.selectByIds(randomIds);
优缺点点评
优点:真・随机,由于用了 Collections.shuffle,随机分布非常均匀;逻辑简单粗暴。
缺点:太占内存。如果表里有 1000 万条 ID,全拉到内存里,JVM 直接 OOM 教做人。
避坑:一定要给 ID 列表加缓存(Redis 或本地缓存),别每次请求都去查全量 ID,那跟直接攻击数据库没区别。
方案二:Limit 偏移法(Limit Offset)
适用场景:数据量大(百万级以上),对随机性要求没那么严苛。
核心思想:给所有数据编个号,随机生成一个“偏移量”,直接跳到那里去拿。
