直接看EXPLAIN ANALYZE(PG)或EXPLAIN FORMAT=JSON+PROFILE(MySQL 8.0+)中的Execution Time或Query_time,而非预估的EXPLAIN;注意驱动表选择、索引覆盖、统计信息是否过期及内存配置。

怎么看JOIN实际花了多少毫秒
直接看执行时间,别猜复杂度。数据库的EXPLAIN ANALYZE(PostgreSQL)或EXPLAIN FORMAT=JSON + PROFILE(MySQL 8.0+)会真实跑一遍并返回各阶段耗时。注意:没加ANALYZE的EXPLAIN只做预估,不反映真实IO和CPU开销。
常见错误现象:EXPLAIN显示“Using index”,但实际慢——可能因为索引覆盖不全,回表次数爆炸;或者统计信息过期,优化器选错驱动表。
- 务必在业务低峰期跑
EXPLAIN ANALYZE,避免缓存干扰 - 关注输出里的
Execution Time(PG)或Query_time(MySQL慢日志),不是Rows_examined - 如果JOIN涉及临时表,留意
Temp table size和磁盘落地(On disk字样)
驱动表选错是性能崩盘最常见原因
JOIN不是对称操作,数据库必须选一个表当“驱动表”(outer table),逐行去另一个表找匹配。选错会导致嵌套循环次数从O(N×M)跳到O(N×M×K)——比如用10万行的大表去驱动500万行的更大表,且没走索引。
使用场景:LEFT JOIN中左表默认是驱动表;INNER JOIN由优化器决定,但可用STRAIGHT_JOIN(MySQL)或/*+ leading(t1) */(Oracle/PG hint)强制指定。
- 检查
EXPLAIN输出的table列顺序:顶部是驱动表 - 驱动表应尽量小、过滤后更小(WHERE条件要先作用于它)
- 被驱动表必须在JOIN字段上有高效索引,否则变成全表扫描 × 驱动表行数
哈希JOIN vs BNL vs 嵌套循环:硬件真正在乎的细节
不同JOIN算法对内存、磁盘、CPU敏感度完全不同。MySQL 8.0+默认优先用Hash Join(内存够时),而老版本靠Block Nested-Loop(BNL),容易触发磁盘临时表;PostgreSQL则根据成本模型在Nested Loop、Merge Join、Hash Join间切换。
性能影响关键点:
-
join_buffer_size(MySQL)太小 → BNL退化成磁盘块循环,IOPS飙升 - 哈希表放不下内存 → 落盘哈希(
Hash join (disk)),速度跌一个数量级 - 若两表JOIN字段都已排序,
Merge Join(PG)可零内存开销,但要求严格有序
统计信息不准会让所有分析失效
优化器依赖表的行数、列值分布(如pg_stats或mysql.innodb_table_stats)估算成本。一旦过期,它可能认为“只有100行”的表其实有200万行,于是选错算法、忽略索引、误判驱动表。
容易踩的坑:
- 大表批量INSERT/DELETE后没
ANALYZE TABLE(MySQL)或VACUUM ANALYZE(PG) - 分区表只
ANALYZE了主表,没对子分区单独更新统计 - 某些云数据库(如Aurora)自动ANALYZE延迟高,需手动触发
真正卡住性能的,往往不是JOIN写法本身,而是统计信息滞后+内存配置僵化+索引覆盖残缺这三者叠在一起。调一条慢SQL,得同时查三处:执行计划里的实际耗时、驱动表选择逻辑、以及那张表的n_tup_ins和last_analyze时间戳。


















