MySQL 取随机行
本文将介绍 MySQL 的取随机行的正确做法。
一、取随机行的需求
在实际开发中,可能会有这样的需求:
从一堆数据中随机取出某几条数据。
例如:
- 从题库中随机取几道题目放入试卷
- 从课程库中随机取几个课程,向用户推荐
对应到数据库层面,便需要从某个数据表中取随机行。
二、通过 order by rand() 实现
1 |
|
对 words 表进行随机排序,取前三个。
这个语句的具体执行流程是:
假如内存中放得下:
创建一个位于内存中的临时表
从 words 表中,按顺序取出 word 值,rand() 生成随机数,将 word 值与随机数作为临时表的一行放入
初始化 sort_buffer
从临时表中逐行取出随机数和 “位置信息”,放入 sort_buffer 中
- 临时表的存储引擎是 MEMORY,并不使用索引,而是使用数组下标定位,这里称为 “位置信息”
- 不直接取 word 的原因是为了避免额外的内存占用,并且对于内存中的临时表额外,回表并不会照成额外的性能损耗
对 sort_buffer 中的记录做排序
取前三个结果,根据 “位置信息” 到临时表中取出 word 值,返回给客户端
假如内存中放不下:
- 需要创建位于磁盘中的临时表
- ···
无论哪种情况,order by rand()
的资源消耗都会很大。
三、通过代码逻辑实现
1. 根据主键大小
做法:
- 获取主键最大值、最小值
- 取主键大于等于
(MAX - MIN) * rand() + MIN
的行
好处:
- 取主键最大最小值都不需要扫描索引
- 取目标行时可以通过聚簇索引快速定位
缺点:
仅适用于主键为数字且递增 的情况
如果表在增删改查过程中产生了主键空洞,则不同行的随机概率将不同
1
2
3insert 1, 2, 3, 4, 5
delete 2, 3, 4
-- 此时这个算法选中 1 的概率为 1/5, 选中 5 的概率为 4/5
2. 根据行数
- 做法:
- 取表的行数
- 通过
limit floor(行数 * rand()) 1
取行
- 好处:
- 可以解决 “根据主键大小” 算法中因主键 ID 空洞而导致的概率不均问题
- 可以适用于任意主键类型的表
- 缺点:
- 首先需要扫描所有行获得行数,然后需要依次扫描随机个行再丢弃,最后需要扫描目标行,因此扫描行数为
所有行数 + 随机数 + 1
,执行代价更高
- 首先需要扫描所有行获得行数,然后需要依次扫描随机个行再丢弃,最后需要扫描目标行,因此扫描行数为
参考
- MySQL 技术内幕
- MySQL 实战 45 讲
- MySQL 是怎样运行的:从根儿上理解 MySQL