news 2026/4/16 16:17:30

SQL 性能避坑:为什么阿里强制禁用 ORDER BY RAND()?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL 性能避坑:为什么阿里强制禁用 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()的执行流程大致是这样的:

  1. 全表扫描:MySQL 需要为每一行数据生成一个随机值。
  2. 创建临时表:把查询列对应的随机值塞进临时表(如果内存不够,还会用到磁盘临时表)。
  3. 全局排序:对临时表里的随机值进行排序。
  4. 取出前几条:这就好比你要从一袋米里随机挑 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)

适用场景:数据量大(百万级以上),对随机性要求没那么严苛。

核心思想:给所有数据编个号,随机生成一个“偏移量”,直接跳到那里去拿。

代码实现
// 1. 先查询总数(可以走缓存) // SQL: SELECT COUNT(*) FROM product; int totalCount = productMapper.count(); // 2. 随机生成一个偏移量 // 注意:totalCount - 3 是为了防止 limit 越界,确保能取够3条 int offset = new Random().nextInt(totalCount - 3); // 3. 直接利用 LIMIT 偏移量查询 // SQL: SELECT * FROM product LIMIT #{offset}, 3; List<Product> results = productMapper.selectByOffset(offset, 3);
优缺点点评
  • 优点:性能极佳!大部分情况下只需要扫描offset + 3行 ,count值可以放缓存中,定期更新。
  • 缺点
    1. 伪随机:你取出来的 3 条数据是物理上连续的。比如正好取出了“iPhone 13, iPhone 14, iPhone 15”,看起来不够随机。
    2. 深分页问题:如果随机到的offset很大(比如 900万),LIMIT 9000000, 3的性能也会下降,因为 MySQL 要先扫过前 900 万行扔掉。

方案三:多次查询法(Multiple Queries)

适用场景:数据量大,且要求高质量随机。

核心思想:既然方案二取出的数据是连续的,那我多随机几次,每次取 1 条,拼凑出 3 条不就行了?

代码实现
// 1. 获取总数 int total = productMapper.count(); // 2. 生成3个不重复的随机下标(Java 8 Stream 写法) List<Integer> randomOffsets = new Random() .ints(0, total) // 生成无限流 .distinct() // 去重 .limit(3) // 截取前3个 .boxed() .collect(Collectors.toList()); // 3. 循环查询(或者拼接 SQL 用 UNION ALL) List<Product> result = new ArrayList<>(); for (Integer offset : randomOffsets) { // SQL: SELECT * FROM product LIMIT #{offset}, 1 result.add(productMapper.selectByLimit(offset, 1)); }

其实这就是MySQL 45讲里推荐的优化思路。相比于方案二,它打散了连续性。

优缺点点评
  • 优点:既避免了全表排序,又保证了较好的随机性。
  • 缺点:要与数据库交互多次(N 次查询)。不过对于高并发应用,一般都是多次查询 + 缓存,这点开销完全可以接受。

方案四:主键范围法(Index Random)

适用场景:ID 必须这是连续的(或空洞很少),追求极致性能。

核心思想:既然LIMIT N, M越往后越慢,那我直接算出随机 ID,用主键索引“跳”过去不就完事了?

代码实现

Java 逻辑处理:

// 1. 获取 ID 范围(minId 和 maxId) // SQL: SELECT MIN(id), MAX(id) FROM product; long minId = productMapper.selectMinId(); long maxId = productMapper.selectMaxId(); // 2. 计算随机起点 // 注意:maxId - minId - 3 是为了保证起点的 id 后面至少还有 3 条数据(假设 ID 连续) // 如果 ID 极其稀疏,这个范围可能需要预留更大 long range = maxId - minId - 3; long randomId = minId + (long)(Math.random() * range); // 3. 执行查询 List<Product> products = productMapper.selectGtId(randomId, 3);

SQL 实现:

SELECT * FROM product WHERE id >= #{randomId} LIMIT 3;
优缺点点评
  • 优点:速度快到飞起!复杂度直接降为 $O(\log N)$(主键查找),完全没有LIMIT深分页的性能衰减。
  • 缺点非常挑食!它假设 ID 是连续的。如果你的商品表里因为删删改改导致 ID 中间空洞很大,这类 SQL 会导致分布严重不均(空洞前的那条数据被选中的概率会暴增),甚至可能取不到数据。

方案五:Redis 预处理法(Redis Set)

适用场景:高并发、高性能、大数据量,标准的互联网大厂打法。

核心思想:既然 MySQL 不擅长做随机,那就别难为它了,交给最擅长的 Redis。

代码实现
// 1. 初始化(只需做一次):把所有商品ID丢进 Redis Set // Redis Key: "all_product_ids" // 2. 利用 Redis 原生命令随机获取 ID // 命令:SRANDMEMBER key count // 时间复杂度:O(N),N是你取的数量,极快 List<Integer> randomIds = redisTemplate.opsForSet().randomMembers("all_product_ids", 3); // 3. 回表 MySQL 查详情(这里全是主键查询,性能无压力) List<Product> products = productMapper.selectByIds(randomIds);
优缺点点评
  • 优点天花板级别的性能。无论你有多少数据,Redis 都基本能在几毫秒内吐出随机 ID。
  • 缺点:架构变复杂了。你需要维护 Redis 和 MySQL 的数据同步(也就是经典的缓存一致性问题)。

最终总结:选型指南

那这几种方案怎么选?

你的场景推荐方案理由
数据量 < 10W方案一(应用层 Shuffle)开发最快,逻辑最简单,随机性最完美。
数据量 > 10W,ID连续方案四(索引跳跃)既不用维护缓存,又能享受极致性能。
数据量 > 10W,允许连续方案二(Limit Offset)性能不错,通用性强,是个老实人。
数据量 > 10W,要求打散方案三(多次查询)在性能和随机性之间找到了平衡点。
高并发 / 追求极致方案五(Redis Set)工业界标准答案,虽然稍微麻烦点,但真香。
想被辞退ORDER BY RAND()只要你敢用,P0 故障随时带回家。

最后多嘴一句
如果你的业务可以接受“伪随机”(比如每个人看到的随机列表在 1 小时内是一样的),强烈建议把算好的随机结果丢进 Redis。毕竟,最好的 SQL 优化就是不执行 SQL

别让你写的代码,成为深夜报警的罪魁祸首。

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

JAVA中对象的几种比较

Java 中对象的几种比较方式详解 Java 中对象的“比较”主要分为两种需求&#xff1a; 判断两个对象是否“相等”&#xff08;内容是否相同&#xff09;判断两个对象的大小关系&#xff08;排序用&#xff09; 对应地&#xff0c;Java 提供了多种机制来实现对象的比较。下面系…

作者头像 李华
网站建设 2026/4/15 15:35:40

Sambert-HifiGan情感控制秘籍:如何合成不同情绪的语音

Sambert-HifiGan情感控制秘籍&#xff1a;如何合成不同情绪的语音 引言&#xff1a;中文多情感语音合成的技术演进与现实需求 随着智能客服、虚拟主播、有声阅读等应用场景的不断扩展&#xff0c;传统“机械化”的语音合成已无法满足用户对自然度和情感表达的需求。中文多情感…

作者头像 李华
网站建设 2026/4/16 7:45:04

【Java毕设全套源码+文档】基于springboot的网络云端日记本系统设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/16 7:41:38

【Java毕设源码分享】基于springboot+vue的学生就业信息管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/16 7:46:38

人物行走卡顿?步数与帧率协同调整方案

人物行走卡顿&#xff1f;步数与帧率协同调整方案 引言&#xff1a;动态生成中的流畅性挑战 在基于 I2VGen-XL 模型的图像转视频&#xff08;Image-to-Video&#xff09;应用中&#xff0c;用户常遇到一个典型问题&#xff1a;人物动作不连贯、行走过程出现明显卡顿。尤其是在生…

作者头像 李华
网站建设 2026/4/16 7:46:55

Sambert-HifiGan语音合成模型的增量训练

Sambert-HifiGan语音合成模型的增量训练&#xff1a;中文多情感场景下的高效优化实践 &#x1f4cc; 引言&#xff1a;为何需要增量训练&#xff1f; 在语音合成&#xff08;TTS&#xff09;的实际落地过程中&#xff0c;预训练模型虽强&#xff0c;但难以覆盖所有业务需求。…

作者头像 李华