最拖后腿的是执行计划中Actual Total Time最高(PG)或Query_time与Rows_examined比例严重失衡(MySQL)的JOIN节点,如Nested Loop配合大表扫描、Seq Scan无索引字段、type=ALL或Using temporary/filesort;需检查JOIN字段类型一致、索引存在且顺序匹配、避免条件中用函数。

看执行计划里哪个Join节点实际耗时最高
不是看“用了Hash Join还是Nested Loop”,而是盯住Actual Total Time(PostgreSQL)或Query_time与Rows_examined比值(MySQL)最大的那个Join子节点。如果Nested Loop外层返回10万行,内层每次查1ms,总耗时就是100秒——但执行计划里可能只标出内层单次耗时,容易误判。
- PostgreSQL用
EXPLAIN ANALYZE,直接找Actual Total Time列数值突兀高于其他节点的Nested Loop或Hash Join - MySQL看慢日志里的
Rows_examined: 1248902但Rows_sent: 12,说明JOIN后严重膨胀或漏写ON条件 - SQL Server在XML执行计划里搜
<RelOp LogicalOp="Hash Join">,再看其子节点EstimateRows和ActualRows是否相差超10倍
检查Join字段是否触发隐式转换
一旦执行计划里出现Compute Scalar或Convert节点,基本就是类型不一致在作祟。比如orders.user_id是BIGINT,users.id是INT,SQL Server会悄悄把后者转成BIGINT,导致索引失效。
- 用
sp_help或SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS确认两边字段类型完全一致 - 避免在
ON里写函数:ON UPPER(a.email) = UPPER(b.email)→ 改为建计算列+索引,或提前标准化存储 - PostgreSQL中
character varying(32)和text不等价,integer和bigint也不兼容,不能靠USING (col)蒙混过关
识别索引没被真正用上的假象
key字段显示非NULL不代表高效——可能只是走了索引但扫描了50%的行,或者回表代价太高。执行计划里type = ref但rows接近全表,大概率是统计信息过期或索引区分度太低。
- 运行
ANALYZE TABLE orders(MySQL)或UPDATE STATISTICS orders(SQL Server)强制刷新统计信息 - 查
pg_stat_all_indexes(PG)或sys.dm_db_index_usage_stats(SQL Server),确认索引user_id的user_seeks是否为0 - 如果
Extra里有Using temporary; Using filesort,说明JOIN后的ORDER BY或GROUP BY没走索引,得补覆盖索引
别忽略锁等待伪装成慢JOIN
查询卡住不动?先查是不是被锁住了。JOIN本身不慢,但可能因为长事务占着users表某几行,导致SELECT u.*, o.* FROM users u JOIN orders o在LCK_M_S上干等。
- SQL Server跑
SELECT * FROM sys.dm_exec_requests WHERE status IN ('suspended', 'running'),重点看wait_type:是LCK_M_S就查blocking_session_id,是PAGEIOLATCH_SH就查磁盘IO - PostgreSQL查
pg_stat_activity,wait_event_type = 'Lock'且state = 'active'但state_change远早于当前时间,基本就是锁冲突 - MySQL没有原生锁视图,但
SHOW PROCESSLIST里看到State为Locked或Waiting for table metadata lock,就得顺藤摸瓜找源头事务
实际排查时最容易被跳过的点:执行计划里EstimateRows和ActualRows偏差巨大,往往意味着统计信息不准,但很多人只盯着索引建没建,忘了更新这一步。

















