相关子查询性能差的根源在于索引失效,而非语法本身;需检查字段类型一致性、联合索引最左前缀匹配、避免函数运算及OR条件导致的全表扫描,并通过EXPLAIN验证key、key_len与Extra字段。

相关子查询本身不必然导致全表扫描,但只要内层查询的 WHERE 条件没走索引,外层每来一行就触发一次无索引扫描——100 行外层数据 = 100 次全表扫描。问题根源不在“子查询”语法,而在关联字段是否类型一致、是否有匹配索引、是否满足最左前缀。
相关子查询里字段类型不一致直接废掉索引
这是最隐蔽也最常踩的坑:外层传进子查询的值和内层字段类型不匹配,MySQL 自动做隐式转换,索引列被“加工”后无法命中。
- 比如内层表
user_id是VARCHAR,外层用u.id = 123(数字)传参 → 子查询变成WHERE user_id = '123'?不,是WHERE CAST(user_id AS SIGNED) = 123,索引失效 - 再如内层
status是TINYINT,外层写WHERE status = '1'(字符串)→ 同样触发转换,type在EXPLAIN中显示ALL - 验证方法:在子查询单独执行,加
EXPLAIN,看key是否为NULL,Extra是否含Using where但没Using index condition
联合索引在子查询中只用了右半部分
子查询里写了 WHERE b = ? AND c = ?,但联合索引是 (a, b, c) —— 这种写法连索引都不会用,type 直接是 ALL。
- 原因不是“子查询特殊”,而是所有查询都遵守最左前缀:跳过
a,(b, c)就是无效组合 - 如果业务高频查
b和c,别硬套原索引,该建(b, c)就建,或改用(b, c, a)覆盖常用查询 -
key_len值是关键线索:查(a, b, c)索引却只显示用了 4 字节(仅a的长度),说明b和c根本没参与索引查找
EXISTS 子句里 OR 条件放大扫描次数
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND (o.status = 1 OR o.amount > 100)) 这类写法,OR 会让优化器放弃使用索引,尤其当 status 区分度低(只有 0/1)时,极易退化为全表扫描驱动。
-
OR左右两边只要有一边没索引,整个条件大概率走全表;即使都有,MySQL 5.7 及更早版本也不合并索引 - 比强行加
/*+ USE_INDEX() */更稳的解法:拆成两个EXISTS用OR连接,或改写为UNION(注意去重开销) - 执行计划里若看到多个
DEPENDENT SUBQUERY且rows列数值巨大,基本可断定是OR+ 无索引组合在反复扫表
子查询里用了函数或计算,索引从根上就断了
哪怕外层关联完美、类型一致、索引结构也对,只要子查询里对索引列做了函数或运算,索引就彻底失效。
- 典型写法:
WHERE YEAR(o.create_time) = 2023、WHERE o.amount * 1.1 > 100、WHERE UPPER(o.name) = 'ALICE' - 这些不是“慢一点”,而是让优化器完全无法用 B+Tree 定位,只能逐行计算再判断
- 修复不是“换个函数”,而是重构条件:用范围代替函数,如
create_time BETWEEN '2023-01-01' AND '2023-12-31';金额比较移项到右边:amount > 100 / 1.1
真正卡住性能的,往往不是“有没有索引”,而是子查询里那一个没加引号的数字、一个跳过左列的条件、一个写在左边的函数——它们不会报错,只会默默把每次关联变成一次全表扫描。检查时盯死 EXPLAIN 里的 key、key_len 和 Extra,比读十遍文档都管用。

















