rand() 给一个 0 到 1 之间的小数,每次调用都不一样。
rand(种子) 给固定的一串,同一个种子永远同一串。
order by rand() limit n 是「随机抽 n 条」——Demo 里连跑两次,抽到的三件完全不同。
代价是全表排序:每行算一个随机数再整体排,索引一点用不上。
大表要换成「随机 id」或「随机 offset」。
上一篇按条件找,这一篇不要条件,要「随便来几条」。 用途很实在:抽奖、随机推荐、随机出题、抽样核对数据。 但它也是被误用最多的函数之一——小表随便写,大表直接拖垮库。
一、两个用法:无种子与有种子
rand() 每次给不同的数,Demo 里给一个四位小数。
所以它不能用来做「可复现」的事——结果没法写进测试用例。
rand(7) 用 7 当种子,结果固定。 同一句里写两次 rand(7),两个数完全一样,
因为每次都从同一个种子重新开始。
规矩:造测试数据、做抽样脚本,一律用带种子的 rand(固定值);
只有「就是要每次不同」的场景才用无种子的。
二、随机抽几条:order by rand() limit n
写法:SELECT ... FROM 表 ORDER BY RAND() LIMIT 3。 Demo 里连跑两次,抽到的三件完全不同。
原理:给每一行算一个随机数,
按这个随机数排序,再取前三条。 所以它是「全表 + 排序」,不是「随便挑三条就走」。
本沙箱的 rand() 是垫片(用 Python 的 random 实现),数值序列跟 MySQL 对不上,
但「每次都不同」这个行为一致。 所以 Demo 里的具体数字不必和 MySQL 的一致。
三、代价:它是「全表排序」
order by rand() 要给每一行算一次随机数、再整体排序,
复杂度是「全表 + 排序」。 索引帮不上忙,临时表也用得上。
十万行以内还能忍,上百万行就是灾难——每次请求都要排一遍全表,并发一上来直接把库拖死。
三种替代:一、先随机一个 id 再取(WHERE id >= 随机值 ORDER BY id LIMIT 3);
二、先 count(*) 拿总数,随机一个 offset 配 LIMIT offset, n;
三、真要「不重复地抽」,把抽过的记下来。
四、一个容易忽略的点:抽到的不是「均匀样本」
order by rand() limit 3 每一行被抽中的概率是相同的,这一点没问题。
但「抽三次、每次三条」不等于「随机分三组」。
做数据抽样核对时要当心:随机抽 100 行看质量,抽到的永远是「随机分布」的 100 行,
按时间、按地区成片出错的那类问题,很可能一条都抽不到。
抽样要分层:按类别各抽几条,比「全表随机抽」更能发现问题。
五、动手 Demo:给种子与不给种子,抽到的完全不是一回事
Demo 四段:先看无种子的 rand(),再给种子固定下来,然后连抽两次三条商品。
注意:抽奖那两句的输出只是其中一次的样本,你自己跑,
抽到的三件一定不是这三件——这正是这一篇的重点。
import _q46 as Q
c = Q.con()
# 无种子:每次都变
c.execute("SELECT CAST(ROUND(RAND(), 4) AS TEXT) AS r")
# 有种子:结果固定
c.execute("SELECT CAST(ROUND(RAND(7), 4) AS TEXT) AS a, "
"CAST(ROUND(RAND(7), 4) AS TEXT) AS b")
# 随机抽三条:两次结果不一样
c.execute("SELECT name FROM goods ORDER BY RAND() LIMIT 3")
c.execute("SELECT name FROM goods ORDER BY RAND() LIMIT 3")
实跑输出:
--------------------------------------------------------------------
第二十一课:rand(),以及它对大表做了什么
--------------------------------------------------------------------
rand() 给一个 0 到 1 之间的小数
SQL> SELECT CAST(ROUND(RAND(), 4) AS TEXT) AS r
r
-------
0.8053
(1 行)
**每跑一次都不一样。** **这正是「随机」的定义,也是它不能用来做「可复现结果」的原因。**
给个种子:结果就固定了
SQL> SELECT CAST(ROUND(RAND(7), 4) AS TEXT) AS a, CAST(ROUND(RAND(7), 4) AS TEXT) AS b
a b
------------------
0.3238 0.3238
(1 行)
**`RAND(7)` 用 7 当种子,两次都算出同一个数。** **换种子就换一串数,同一个种子永远同一串。**
**所以测试里要随机数据,别用 `RAND()`,用 `RAND(固定种子)`**——否则今天过、明天挂,查都没法查。
随机取几条:order by rand() limit n
SQL> SELECT name FROM goods ORDER BY RAND() LIMIT 3
name
-------
运动鞋
护腕
瑜伽垫
(3 行)
SQL> SELECT name FROM goods ORDER BY RAND() LIMIT 3
name
---------
篮球
乒乓球拍
跳绳
(3 行)
**两次抽到的三件不一样。** **这段输出只是其中一次的样本**,你再跑一次,三件肯定又变了。
**它的原理**:给每一行算一个随机数,**按这个随机数排序,再取前三条。** **所以是全表排序**,
不是「随便挑三条就走」。
代价:十万行才看得出问题
**`order by rand()` 要给每一行都算一次随机数、再整体排序**,
**复杂度是「全表 + 排序」,索引一点用不上。**
**小表随便用,几万行以上就得换思路**:
**一、先随机一个 id 区间再取**:`where id >= 随机值 order by id limit 3`;
**二、程序里先 `count(*)` 拿到总数,随机一个偏移量配 `limit offset, n`;**
**三、真要「每次都不重复地抽」,把抽过的记下来,别靠 `rand()` 硬碰。**
**顺带一句**:`rand()` 是 MySQL 的函数,标准 SQL 里没有。
**本沙箱的 `rand()` 是垫片**(用 Python 的 random 实现的),
**数值序列跟 MySQL 对不上,但「每次都不同」这个行为是一致的。**
第一段无种子。 给一个四位小数,每次都不一样。
第二段给种子。 rand(7) 两次都是 0.3238——同一种子同一串,这才叫可复现。
第三段抽两次。 六件商品,抽出来的三条两次都不同。
这不是「随机数变了」,是「排序结果变了」。
第四段没有 SQL,
全在讲代价与三种替代写法——order by rand() 的开销才是这一篇最该带走的东西。
30 秒实验
- 把
RAND(7)换成RAND(8),两个数还是 0.3238 吗? ORDER BY RAND() LIMIT 3改成LIMIT 6,第二次还会换吗?为什么?- 连续跑三次同一句
ORDER BY RAND() LIMIT 3,有没有可能两次抽到一样的?
常见误区
- 在测试里用无种子的
rand()。 今天过、明天挂, 而且查不出原因——因为「昨天那个随机数」再也复现不了。 - 在百万行的表上写
order by rand()。 每次请求都排一遍全表,几个人同时点就把库拖死。 - 以为随机抽样能查出所有问题。 它按行均匀抽,成片出错(按时间、按地区、 按批次)的那类问题经常一条都抽不到。
自测三问(点开对答案)
1. `rand()` 与 `rand(7)` 有什么区别? —— `rand()` 每次给不同的数;`rand(7)` 用 7 当种子,同一个种子永远给同一串数。 2. `order by rand() limit 3` 是怎么实现的? —— 给每一行算一个随机数,按它整体排序,再取前三条。所以是全表加排序。 3. 大表要随机取几条,怎么写更稳? —— 先随机一个 id 区间再取,或者先 `count(*)` 拿到总数、随机一个 offset 配 `limit`。like 和 rand() 都只在一条语句里折腾。 下一篇把一条查询的结果喂给另一条——子查询。
- ☐ (本篇完,下一篇 5.3 子查询)