MySQL中RAND()抽样越查越慢是因为ORDER BY RAND()导致全表扫描和全字段随机排序;PostgreSQL严格等概率抽样需用ORDER BY RANDOM() LIMIT N,配合覆盖索引可提升性能。

MySQL 中 RAND() 抽样为什么越查越慢?
因为 RAND() 在 ORDER BY 中对每行都重新计算,数据量一过几万,排序成本就指数级上涨。不是函数本身慢,是它触发了全表扫描 + 全字段随机排序。
- 别用
SELECT * FROM t ORDER BY RAND() LIMIT 10查百万级表——实际执行会扫描全部行再排序 - 如果只要少量样本(比如 0.1%),优先考虑基于主键的随机跳过:
WHERE id >= FLOOR(RAND() * (SELECT MAX(id) FROM t)) LIMIT 10,但要留意主键不连续时抽样偏差 - 更稳的做法是先用子查询生成随机 ID 列表(比如从
information_schema或预估范围采样),再JOIN回原表
PostgreSQL 怎么写等概率随机抽样才不丢精度?
PostgreSQL 的 TABLESAMPLE 看似简单,但默认 BERNOULLI 是按行独立掷硬币,小表可能抽到 0 行;SYSTEM 按页抽样,在有长文本或大字段时严重偏态。
- 要严格控制样本数且等概率,必须用
ORDER BY RANDOM() LIMIT N,虽然也扫全表,但结果可预期 - 想提速?给常用查询字段建覆盖索引(比如只查
id, name,就建INDEX ON t(id, name)),让排序走索引而不是堆表 - 注意:
RANDOM()在同一语句中多次出现是不同值,别写成WHERE RANDOM() ——两次调用不相等
SQL Server 用 NEWID() 抽样卡死怎么办?
ORDER BY NEWID() 确实能等概率抽样,但它强制把整张表所有行的 NEWID() 值算出来再排序,内存和 CPU 压力极大,尤其在并行度高的 OLTP 环境里容易拖垮其他查询。
- 替代方案:用
TABLESAMPLE (n PERCENT),但得接受它不保证精确数量,且对聚集索引不友好 - 折中做法:先
SELECT TOP 10000 id FROM t ORDER BY NEWID()(限制中间结果集大小),再用这些id去主表查完整字段 - 如果表有自增
IDENTITY列,可以结合CHECKSUM(NEWID()) % N做分桶近似抽样,比全排序轻量得多
跨数据库通用的「伪随机」安全抽样技巧
真正需要可复现、可审计的抽样(比如 A/B 测试或审计抽样),不能依赖每次执行都变的 RAND() 或 NEWID()。得把随机种子固化下来。
- MySQL 8.0+ 支持
RAND(N),传固定整数 N 就能复现同一序列,例如ORDER BY RAND(42) LIMIT 100 - PostgreSQL 可用
setseed(0.42)配合RANDOM(),但要注意该设置只对当前 session 有效 - 最保险的是把“随机”移出 SQL:用应用层生成一批哈希值(如
HASH(id || '2024-q3-audit')),再取模筛选,这样逻辑清晰、可测试、不依赖数据库随机实现细节
抽样这事,表面是函数怎么写,实际是“你要的到底是统计意义的随机,还是工程可控的确定性”。后者往往更重要,只是容易被忽略。

















