必须重写SQL而非加索引的情况包括:EXPLAIN显示全表扫描且rows值高,且WHERE含函数(如YEAR(created_at)=2023)、隐式类型转换(如user_id='123')或OR多非覆盖条件;此时应改函数为范围查询、避免隐式转换、拆分OR条件。
什么时候必须重写 SQL 而不是加索引
索引解决不了所有性能问题。当
显示全表扫描 + 高
值,且 WHERE 条件里有函数调用(如
)、
隐式类型转换
(如
而
是
)、或
连接多个非覆盖条件时,索引很可能失效——这时重写比调优更有效。
函数包裹列 → 改成范围查询:
多条件 → 拆成
(注意去重开销)或改用
(当语义等价时)
隐式转换
→ 统一数据类型:
(而非字符串)
JOIN 顺序与驱动表选择的实际影响
MySQL 5.7+ 默认使用基于成本的优化器,但小表驱动大表仍是可靠原则。如果
中
是
且
极高,说明该表被当作驱动表,而它本身又没走索引——这是重写的明确信号。
强制小表在前:把过滤后结果集最小的表放在
后第一个位置
避免
后再
过滤右表字段(会退化为
),应把条件移到
子句
多表 JOIN 时,用
(MySQL)临时固定顺序,验证效果后再决定是否保留
子查询转 JOIN 或窗口函数的取舍
相关子查询(correlated subquery)是性能黑洞,尤其在外部表数据量大时。但并非所有子查询都该转——要看数据库版本、数据分布和语义约束。
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
标量子查询(
)→ 优先转
,除非
不唯一需保语义
存在性检查(
)→ 可用
替代,但注意重复行;更稳的是保持
并确保
有索引
MySQL 8.0+ / PostgreSQL 可用窗口函数替代部分聚合子查询,例如用
替代“每类最贵商品”类子查询
GROUP BY 和 HAVING 的隐藏开销点
本身不慢,慢在分组键未索引、或
中用了无法下推的表达式。如果
显示
,且分组字段无索引,重写重点就在这里。
确保
字段有联合索引,顺序与分组顺序一致(如
→ 索引应为
)
无法下推,但若能提前用
过滤(如
),先缩小输入集再分组,效果远好于依赖
避免在
中用函数或表达式(如
),改用生成列(MySQL 5.7+)或预计算字段
真正卡住的往往不是语法多复杂,而是没意识到某个
条件本可以提前过滤掉 90% 的行——重写的第一步,永远是看懂执行计划里哪一行真正扫了最多数据。
EXPLAINrowsWHERE YEAR(created_at) = 2023WHERE user_id = '123'user_idINTORWHERE created_at >= '2023-01-01' AND created_at ORUNION ALLINWHERE user_id = 123EXPLAINtypeALLrowsFROMLEFT JOINWHEREINNER JOINONSTRAIGHT_JOINSELECT (SELECT name FROM users WHERE id = t.user_id)JOINusers.idWHERE EXISTS (SELECT 1 FROM logs l WHERE l.order_id = o.id)INNER JOINEXISTSlogs.order_idROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC)GROUP BYHAVINGEXPLAINUsing temporary; Using filesortGROUP BYGROUP BY dept, role(dept, role)HAVING COUNT(*) > 1WHEREWHERE status = 'active'HAVINGGROUP BYGROUP BY DATE(created_at)WHERE