主键范围导出快的前提是真正走主键聚簇索引顺序扫描,而非被优化器误判为全表扫描;需验证explain中key为primary、rows接近预期值、extra无using filesort/using temporary。

主键范围导出快,前提是导出语句真正走的是主键聚簇索引的顺序扫描,而不是被隐式改写或优化器误判成全表扫描。很多“按ID导出”的脚本实际性能差,问题不出在SQL写法本身,而在数据分布、主键类型和执行路径没被验证过。
为什么 SELECT ... WHERE id BETWEEN ? AND ? 仍可能慢
看似标准的主键范围查询,实际执行时容易掉进几个坑:
-
id是 UUID 或雪花 ID:逻辑上连续的 ID,在磁盘上物理页完全离散,BETWEEN扫描仍触发大量随机 I/O,缓存命中率低 - WHERE 条件里混用了函数,比如
WHERE CAST(id AS CHAR) LIKE 'a1b2%',直接导致主键索引失效 - 目标区间跨度太大(如
BETWEEN 1 AND 1000000),但read_rnd_buffer_size过小,MRR 无法生效,回表或排序反而拖慢整体导出 - 导出语句加了
ORDER BY created_at,而created_at不在主键里,优化器放弃利用主键顺序,转而用 filesort + 临时表
导出前必须验证的三件事
别只看 EXPLAIN 的 type=range,重点检查这几项:
-
key字段是否为PRIMARY—— 如果是其他索引名,说明根本没走主键 -
rows是否接近你预期的主键区间长度(比如BETWEEN 5000 AND 5099,rows应该≈100;若显示 50000,大概率走了全表或错误索引) -
Extra里有没有Using where; Using index(覆盖索引)、Using index condition(ICP 生效),或出现Using filesort/Using temporary(已退化)
更准的方式是跑一次 EXPLAIN FORMAT=JSON,看 query_block.nested_loop.access_type 是否为 range,以及 index_condition 是否包含主键字段。
真正高效的主键范围导出写法
不是所有 BETWEEN 都等价。以下写法能稳定触发聚簇索引顺序读取:
- 用闭区间,避免
id >= ? AND id 写法 —— 虽然语义等价,但某些 MySQL 版本对 <code>BETWEEN的 range 估算更准确 - 导出字段尽量精简,优先用
SELECT id, name, status而非SELECT *;若只需主键+少量列,考虑建覆盖索引减少回表(但注意:覆盖索引 ≠ 聚簇索引,它仍是二级索引) - 大范围导出时,手动分片比单次大查询更稳:
WHERE id BETWEEN 10000 AND 19999、20000 AND 29999……避免rows估算偏差放大 - 确认
optimizer_switch中mrr=on且mrr_cost_based=on(默认开启),这对辅助索引导出有帮助,但对主键范围扫描影响不大
UUID/雪花ID主键的导出必须换思路
如果你的 id 是 CHAR(32) 或 BIGINT 类雪花 ID,物理无序性会让主键范围扫描失去意义。这时不能硬靠 BETWEEN,得换路径:
- 加时间维度过滤:如果表有
created_at且该字段有索引,优先用WHERE created_at >= ? AND created_at ,再配合 <code>ORDER BY created_at, id稳定分页 - 用自增代理键导出:给表加一个
auto_increment列(如seq_id),仅用于导出分片,不参与业务逻辑 - 禁用排序干扰:导出时明确加
ORDER BY id反而可能触发 filesort;若业务不要求顺序,干脆去掉ORDER BY,让 MySQL 按聚簇索引自然顺序返回(即 B+ 树叶子节点遍历顺序)
最易被忽略的一点:即使主键是自增整型,如果导出语句里用了 LIMIT + 大 OFFSET(如 LIMIT 100000, 1000),MySQL 仍要跳过前 10 万行数据页 —— 这不是范围扫描,是深度偏移,I/O 成本陡增。这时候应该用游标式分页(WHERE id > last_seen_id LIMIT 1000)替代。











