EXPLAIN中rows飙至亿级,根本原因是优化器选错驱动表或关联字段未走索引,导致中间结果集失控膨胀,如a(1000万行)与b(1500万行)无索引JOIN,产生15万亿次比对。

直接写 JOIN 会卡死或超时,不是 SQL 写错了,而是数据库被迫做全表扫描叠加 + 笛卡尔积预计算。核心破局点就三个:索引字段顺序必须匹配 ON 条件、中间结果必须物化、驱动表必须可控。
为什么 EXPLAIN 显示 rows 飙到亿级?
根本原因是优化器选错驱动表,或关联字段没走索引,导致某次 JOIN 前的中间结果集失控膨胀。比如 a JOIN b JOIN c,若 a 和 b 都无过滤条件,a.id = b.a_id 又没索引,a(1000万行)每行都得扫 b 全表(1500万行),光这一步就是 15万亿次比对。
-
EXPLAIN中某行rows突然跳高 10 倍以上,那行就是膨胀源头 -
type = ALL或Extra含Using join buffer是危险信号 - 检查
JOIN字段类型是否严格一致:INT对BIGINT、VARCHAR(50)对VARCHAR(100)都会让索引失效
复合索引怎么建才真正生效?
不能只在单个外键字段上建索引。要按 ON 条件中“被驱动表”的访问路径建复合索引——先定位,再取值。
- 若写
SELECT * FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id,则:-
b表需建idx_b_a_id_id:CREATE INDEX idx_b_a_id_id ON b (a_id, id)—— 让b被a快速定位后,能直接用id找下一级 -
c表若还带WHERE c.status = 'done',就得建idx_c_b_id_status:CREATE INDEX idx_c_b_id_status ON c (b_id, status)
-
-
a.id和b.id是主键,不用额外建;但b.a_id没索引 = 外键形同虚设
怎么强制中间结果不爆炸?
三张千万级表别一口气连完。拆成两段,且中间表必须显式落盘并加索引,否则优化器仍可能重排执行顺序或复用逻辑视图。
- MySQL:用
CREATE TEMPORARY TABLE temp_ab AS SELECT ...固化前两表结果,再立刻建索引:CREATE INDEX idx_temp_ab_b_id ON temp_ab (b_id) - PostgreSQL:用
WITH RECURSIVE或普通CTE时,必须加MATERIALIZED(v12+),否则仍是逻辑视图 - MySQL 8.0+ 的 CTE 默认不物化,得靠
CREATE TABLE AS WITH强制固化
驱动表不听指挥怎么办?
当 EXPLAIN 显示最大表成了驱动表,硬控比等优化器靠谱。但前提是统计信息已更新(ANALYZE TABLE)。
- MySQL 加
STRAIGHT_JOIN:SELECT STRAIGHT_JOIN * FROM small_table s JOIN big_table b ON s.id = b.s_id,强制s为驱动表 - PostgreSQL 临时关闭全表扫描:
SET enable_seqscan = off,逼它走索引(仅调试用,别放生产) - LEFT JOIN 后对右表字段加
WHERE等于变INNER JOIN,正确写法是把过滤挪进ON子句:LEFT JOIN b ON a.id = b.a_id AND b.status = 'paid'
真正难的不是让 SQL 跑通,而是确保每张大表在关联时只触达必要行数。漏掉索引字段顺序、中间结果物化、驱动表控制中的任意一个,性能就断崖下跌——这三个点,少一个,查询就可能从秒级变成分钟级。


















