SQL改写需基于执行计划、数据分布和索引机制优化:避免SELECT *,用EXISTS替代IN(外大内小场景),拆分OR为UNION ALL,聚合前置减少中间结果集。

SQL改写不是简单换写法,而是通过理解执行计划、数据分布和索引机制,用更贴近优化器偏好的方式表达业务逻辑。真正有效的改写,往往能让慢查询从几秒降到几十毫秒,甚至消除全表扫描。
避免SELECT *,只取真正需要的字段
全字段查询会增加I/O、网络传输和内存开销,尤其当表中存在TEXT、BLOB或宽字段时,性能衰减明显。优化器也可能因此放弃使用覆盖索引。
- 明确列出所需列,例如用SELECT user_id, name, status代替SELECT *
- 在JOIN场景中,注意避免跨表重复取相同语义字段(如多张表都有created_time,只取业务真正依赖的那一侧)
- 对视图或子查询,同样要精简输出列——外层即使只用1个字段,内层若返回全部列,开销已产生
用EXISTS替代IN处理子查询(尤其外大内小)
当主表数据量大、子查询结果集较小时,IN可能触发临时表+全量匹配,而EXISTS可利用关联字段索引快速短路判断。
- 把WHERE order_id IN (SELECT order_id FROM refund WHERE status = 'success')改为WHERE EXISTS (SELECT 1 FROM refund r WHERE r.order_id = o.order_id AND r.status = 'success')
- 确保子查询中的关联字段(如r.order_id)有索引,否则EXISTS优势消失
- 若子查询结果集很大(比如上百万),反而要考虑IN配合临时表或物化,再走HASH JOIN
拆分复杂OR条件,善用UNION ALL
多个OR分支常导致索引失效,优化器倾向走全表扫描。将逻辑独立的分支拆成多个查询并用UNION ALL合并,可让每个分支单独走索引。
- 原写法:WHERE status = 'paid' OR (status = 'shipped' AND updated_at > '2024-01-01')
- 改写后:(SELECT ... WHERE status = 'paid') UNION ALL (SELECT ... WHERE status = 'shipped' AND updated_at > '2024-01-01')
- 注意:用UNION ALL而非UNION,避免去重开销;各分支结果结构必须一致
- 分支数不宜过多(一般≤3),否则调度和合并成本上升,需结合实际执行计划权衡
聚合逻辑前置,减少中间结果集
在JOIN之后再聚合(如COUNT、SUM),容易因笛卡尔积放大中间数据量。优先在单表上完成过滤与聚合,再与其他表关联。
- 低效:SELECT u.name, COUNT(o.id) FROM user u LEFT JOIN order o ON u.id = o.user_id GROUP BY u.id(用户无订单也会参与JOIN,产生空行)
- 优化:SELECT u.name, COALESCE(t.cnt, 0) FROM user u LEFT JOIN (SELECT user_id, COUNT(*) AS cnt FROM order GROUP BY user_id) t ON u.id = t.user_id
- 对高频统计场景,可考虑预聚合到汇总表,或用物化视图(PostgreSQL/Oracle支持)降低实时计算压力











