跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

SQL嵌套查询如何避免全表扫描_合理使用索引辅助子查询

IN子查询变慢因MySQL 5.6前转为嵌套循环,5.7+需满足无GROUP BY等条件才启用半连接优化;应改用EXISTS、确保索引或缓存结果。 子查询里用
IN
为什么突然变慢? 因为 MySQL 5.6 之前,
IN (SELECT ...)
会被转成嵌套循环(NLJ),外层每行都执行一次子查询,等价于全表扫描子查询表。5.7+ 虽支持半连接优化(semi-join),但前提是子查询不能含
GROUP BY
、
DISTINCT
、聚合函数或相关子查询。 检查执行计划:看到
type: ALL
或
Extra: Using where; Using join buffer
就是危险信号 把
IN (SELECT id FROM t2 WHERE ...)
改成
EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id AND ...)
,让优化器更可能走索引 如果必须用
IN
,确保子查询结果集小且
t2.id
有索引——否则先用临时表缓存结果再
JOIN
WHERE
子句里写相关子查询,索引还有效吗? 不一定。相关子查询(correlated subquery)每次都要重新计算,如果子查询里没用上外层字段的索引条件,就容易退化成对子表的重复全扫。 典型陷阱:
SELECT * FROM orders WHERE status = 'paid' AND user_id IN (SELECT id FROM users WHERE region = 'CN')
—— 这里
users
表没被
orders
驱动,
region
索引可能被忽略 改写优先级:能
JOIN
就别用相关子查询;
JOIN
后加
WHERE
过滤,比在子查询里过滤更易命中索引 如果非用不可,确保子查询中用于关联的字段(如
users.id
)和过滤字段(如
users.region
)都有联合索引,比如
INDEX(region, id)
子查询返回多列时,
SELECT *
会拖垮性能? 会。尤其是子查询作为派生表(derived table)出现在
FROM
子句时,MySQL 会物化结果到临时表,默认用 MyISAM(旧版)或 InnoDB(8.0+),没索引、不压缩、全字段存储。 只选需要的列:
SELECT a.id, a.name FROM (SELECT id, name FROM users WHERE active = 1) AS a
比
SELECT * FROM (SELECT * FROM users ...)
少读字段、少内存、更容易走覆盖索引 如果子查询本身能走覆盖索引(例如
SELECT id, email FROM users WHERE status=1
,且
INDEX(status, id, email)
存在),物化开销会大幅降低 8.0+ 可考虑用 CTE +
MATERIALIZED
提示(需 optimizer_switch 启用),但不如直接改写为
JOIN
可控
LIMIT
和子查询一起用,为什么索引失效? 因为
LIMIT
在子查询里生效范围有限——它只限制子查询输出行数,不改变其扫描方式。优化器仍可能选择全表扫描后再取前 N 行。 错误写法:
SELECT * FROM orders WHERE user_id IN (SELECT id FROM users ORDER BY created_at DESC LIMIT 10)
——
users
表仍可能全扫 正确思路:先把子查询结果落地,比如用
CREATE TEMPORARY TABLE tmp_users AS SELECT id FROM users ORDER BY created_at DESC LIMIT 10
,再
JOIN
;或者用
JOIN
+
LIMIT
外推(需业务允许近似结果) 注意:MySQL 不支持子查询中对非主键字段
ORDER BY ... LIMIT
的索引下推,除非该字段在索引最左前缀上 最常被忽略的一点:子查询是否“可展开”(subquery flattening)取决于优化器成本估算,而估算依赖统计信息准确度。表数据量变化后没
ANALYZE TABLE
,即使写了正确结构,也可能走错执行计划。

相关文章