首页/数据/数据库速查/查询改写常用套路
SQL 语法52 条

查询改写常用套路

把 OR 改 UNION、把子查询改 JOIN、把 COUNT 估算化、把深分页改游标;改写前先 EXPLAIN 对照,避免凭感觉重写。

数据库速查数据更新于 2026-10-13

查询改写是什么

查询改写是在语义完全等价的前提下换一种写法,让优化器能利用索引或减少扫描量。常被用到的几类:把 OR 条件改写成 UNION ALL 以便各自命中索引、把超长 IN 列表改成 JOIN 临时表、把深分页改成基于游标的翻页、把精确 COUNT 换成估算值。它的纪律是改写前后都用 EXPLAIN 与结果集对照,凭感觉重写是事故来源。

基本信息

目标语义等价前提下减少扫描行数或提升索引利用率
常见手法OR 改写、IN 列表转 JOIN、深分页优化、COUNT 估算、子查询转连接
验证方式改写前后 EXPLAIN 对照,并比对结果集与行数
风险点UNION 与 UNION ALL 去重语义不同,NULL 与排序语义需逐项确认
适用前提列上有可用索引,改写才有意义

典型使用场景

  • 宽范围 OR 查询:两个不同列上的 OR 无法同时用索引,拆成两条再合并
  • 超长 IN 列表:几千个 ID 拼进 IN 会让解析与优化都很慢,改成 JOIN 临时表
  • 深分页:LIMIT 1000000, 20 需要扫描并丢弃大量行,改成基于上次最大主键的游标翻页
  • 大表计数:精确 COUNT 代价高且常常没人看,监控口径改用估算值
  • 子查询转连接:相关子查询在部分版本上执行效率差,改写成 JOIN 更可控

OR 改写与深分页

OR 改写与深分页
-- 两个不同列上的 OR 往往退化成全表扫描
SELECT id FROM appdb.orders WHERE user_id = 42 OR sku = 'SKU-1';
-- 改写为两条各自走索引的查询再合并去重
SELECT id FROM appdb.orders WHERE user_id = 42
UNION
SELECT id FROM appdb.orders WHERE sku = 'SKU-1';
-- 深分页:偏移量大时扫描并丢弃大量行
SELECT id, created_at FROM appdb.orders ORDER BY id LIMIT 1000000, 20;
-- 改写为游标翻页,只扫需要的那一段
SELECT id, created_at FROM appdb.orders WHERE id > 1234567 ORDER BY id LIMIT 20;
-- 大表计数先用估算值,避免长时间扫描
SELECT table_rows FROM information_schema.tables
WHERE table_schema = 'appdb' AND table_name = 'orders';

改写手法与注意

手法收益必须核对
OR 拆成 UNION各分支都能命中索引UNION 去重与 UNION ALL 语义差别
超长 IN 改 JOIN 临时表减少解析与优化开销临时表需要索引,否则连接本身变慢
深分页改游标避免丢弃大量行业务上能否接受恒定排序键与不支持跳页
精确 COUNT 改估算避免大表长时间扫描对账与决策场景不能用估算值
子查询改连接执行计划更稳定结果集行数是否因一对多而放大
改写的心跳检查:把改写前后的结果集分别导出,做行数与关键字段比对(包括 NULL 处理与去重),确认完全一致后再上生产。
深分页还有一种常见做法是延迟关联:先用覆盖索引取出主键,再按主键回表取完整行,适合必须跳页但仍想减少回表次数的场景。

易错点

  • 把 OR 换成 UNION 却忽略去重差异:原语句可能返回重复行而 UNION 会去重,行数与业务结果都变了
  • 改写后不比对结果集:只看执行计划变好就上线,用错 UNION ALL 或连接条件导致数据错误
  • 深分页改成游标后不支持跳页:产品需求仍需跳页时方案不成立,改动前先对齐需求
  • 把估算值用于对账与报表口径:information_schema 的行数是估算,只适合监控展示
  • 在无可用索引的列上做改写:换写法不会变快,浪费排查时间,先确认索引与选择性问题

数据来源与参考

  • MySQL 8.0 Reference Manual: Optimizing SELECT Statements

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌