SQL 通常分为三大类:
| 类型 | 全称 | 作用 | 常见命令 |
|---|---|---|---|
| DDL | Data Definition Language | 定义/修改结构 | CREATE,ALTER,DROP,TRUNCATE |
| DML | Data Manipulation Language | 操作数据内容 | SELECT,INSERT,UPDATE,DELETE |
| DCL | Data Control Language | 控制访问权限 | GRANT,REVOKE |
SELECT VERSION();
查看MySQL的版本
核心思想是:给按时间排序后的每一行分配一个连续序号,然后每隔一定间隔取一条(或按比例取),从而实现“时间轴上的均匀采样”。
✅ 推荐方法:使用ROW_NUMBER()+ 比例过滤(MySQL 8.0+)
✅ 要求:MySQL 8.0 或更高版本(支持窗口函数)
📌 最佳实践 SQL:
-- 方法1:精确抽取 30%(推荐) INSERT INTO target_table (id, user_id, amount, create_time, status) SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn, COUNT(*) OVER () AS total_rows FROM source_table WHERE status = 'paid' -- 可选:先过滤 ) t WHERE rn <= total_rows * 0.3;🔍 原理解释:
ROW_NUMBER() OVER (ORDER BY create_time)→ 按时间升序编号:1, 2, 3, ..., NCOUNT(*) OVER ()→ 获取总行数Nrn <= N * 0.3→ 取前 30% 的行- 因为已按时间排序,前 30% 就是时间最早的 30%❌(这不是“均匀”!)
⚠️ 注意:上面这个方法是“取前30%”,不是“均匀分布”!
✅ 正确实现“时间均匀分布”的方法
要实现在整个时间范围内均匀抽取 30%(比如每10分钟抽3条,而不是只抽前3个月),应该用等间距采样:
✅ 方法2:等间距采样(真正均匀)
-- 真正的时间均匀采样:每隔 k 行取 1 行,使得最终约 30% INSERT INTO target_table (id, user_id, amount, create_time, status) SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn FROM source_table WHERE status = 'paid' ) t WHERE MOD(rn - 1, 3) = 0; -- 每3行取1行 ≈ 33.3%但 33.3% ≠ 30%。要精确 30%,可这样:
✅ 方法3:精确 30% 均匀采样(最优解)
INSERT INTO target_table (id, user_id, amount, create_time, status) SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn, COUNT(*) OVER () AS total FROM source_table WHERE status = 'paid' ) t WHERE MOD(rn * 100, total) < 30;但这较复杂。更简单且实用的做法是:
✅ 方法4:使用NTILE分桶(推荐!)
-- 将数据按时间分成 10 桶,每桶取 3 桶 → 精确 30% INSERT INTO target_table (id, user_id, amount, create_time, status) SELECT id, user_id, amount, create_time, status FROM ( SELECT *, NTILE(10) OVER (ORDER BY create_time) AS bucket FROM source_table WHERE status = 'paid' ) t WHERE bucket IN (1, 2, 3); -- 取前3桶 = 30%✅这是最符合“时间均匀”要求的方法!
NTILE(10)把按时间排序的数据均分为10个连续时间段- 每个
bucket包含约 10% 的数据- 取
bucket IN (1,2,3)→ 覆盖整个时间范围的前30%时间段?❌
等等!这还是连续时间段,不是“均匀分布”。
✅ 终极正确方案:系统抽样(Systematic Sampling)
目标:在整个时间序列中等间隔抽取,确保样本覆盖全年,不聚集在某一段。
✅ 最佳 SQL(MySQL 8.0+):
-- 计算采样步长:每 k 行取 1 行,使得总数 ≈ 30% WITH numbered AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn FROM source_table WHERE status = 'paid' ), params AS ( SELECT MAX(rn) AS total, CEIL(MAX(rn) / (MAX(rn) * 0.3)) AS step -- 实际可简化 FROM numbered ) SELECT n.* FROM numbered n CROSS JOIN params p WHERE MOD(n.rn - 1, FLOOR(p.total / (p.total * 0.3))) = 0 LIMIT FLOOR((SELECT COUNT(*) FROM source_table WHERE status = 'paid') * 0.3);太复杂了!
✅ 简洁高效方案(生产推荐)
直接用
MOD(ROW_NUMBER(), k) = 0,选择合适的 k
假设总行数 ≈ 2,000,000,要抽 600,000 条(30%),则:
- 采样间隔 k = 总行数 / 目标行数 ≈ 2000000 / 600000 ≈ 3.33
- 但
MOD只能用整数,所以取k = 3 或 4
为了更精确,我们可以动态计算:
✅ 最终推荐 SQL(简洁 + 均匀 + 高效):
-- 方案A:固定间隔(简单有效) INSERT INTO target_table SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn FROM source_table WHERE status = 'paid' ) t WHERE MOD(rn, 3) = 1; -- 每3行取1行 → ~33.3%,接近30%如果一定要严格 30%,且接受轻微不均匀,可用:
✅ 方案B:结合 LIMIT(精确数量 + 均匀起点)
-- 先确定起始偏移,再等间隔取 SET @total = (SELECT COUNT(*) FROM source_table WHERE status = 'paid'); SET @target = FLOOR(@total * 0.3); SET @step = CEIL(@total / @target); PREPARE stmt FROM ' INSERT INTO target_table SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn FROM source_table WHERE status = "paid" ) t WHERE MOD(rn - 1, ?) = 0 LIMIT ? '; EXECUTE stmt USING @step, @target; DEALLOCATE PREPARE stmt;✅ 这个方案:
- 按时间排序
- 从第1行开始,每隔
@step行取1条- 最多取
@target条(精确 30%)- 样本均匀分布在整个时间轴上
📊 效果对比
表格
| 方法 | 是否均匀 | 是否精确30% | 性能 | 复杂度 |
|---|---|---|---|---|
ORDER BY RAND() LIMIT | ❌ 随机聚集 | ✅ | ⚠️ 极差 | 低 |
ROW_NUMBER() <= 0.3*N | ❌ 只取早期 | ✅ | ✅ 高 | 低 |
MOD(rn, 3) = 1 | ✅ 均匀 | ❌ ~33% | ✅✅ 极高 | 低 |
| 动态步长 + LIMIT | ✅ 均匀 | ✅ 精确 | ✅ 高 | 中 |
✅ 结论:用哪条?
如果你接受≈30%(如33%),用这条(最简单高效):
INSERT INTO target_table SELECT id, user_id, amount, create_time, status FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn FROM source_table WHERE status = 'paid' ) t WHERE MOD(rn, 3) = 1;如果你必须严格30% + 均匀,用动态步长方案(见上文)。
💡绝大多数场景下,
MOD(rn, 3) = 1已足够好:它简单、快、真正时间均匀,且比例接近30%。
只要你的 MySQL 是8.0+,这些方案都能高效运行(200万行通常 < 30秒)。