看到 Seq Scan 就该警觉,因其在大表或 JOIN 右表上必然导致性能瓶颈;需用 EXPLAIN (ANALYZE, BUFFERS) 验证真实执行情况,重点检查 Rows Removed by Filter、JOIN 字段索引与类型一致性、ON/WHERE 位置、统计信息新鲜度及索引有效性。

执行计划里看到 Seq Scan 就该警觉
PostgreSQL 或 MySQL 的执行计划中一旦出现 Seq Scan(全表扫描),尤其在 JOIN 的右表或大表上,基本就是性能瓶颈的直接证据。这不是“可能慢”,而是“一定慢”——数据量翻倍,耗时大概率也翻倍。
实操建议:
- 用
EXPLAIN (ANALYZE, BUFFERS)跑真实查询,别只看EXPLAIN;ANALYZE才会触发实际执行并反馈真实耗时与行数 - 重点盯住
Rows Removed by Filter:数值越大,说明 WHERE 或 ON 条件没走索引,靠扫描后过滤,非常低效 - 如果 JOIN 字段类型不一致(比如左表是
INT,右表是TEXT),即使建了索引,优化器也可能弃用——类型隐式转换会拦在索引下层
JOIN 字段没索引?先补最简单的那个
很多人以为“主键自动有索引,JOIN 就没问题”,但错在忽略了 JOIN 是双向的:左表驱动右表时,右表的 JOIN 字段必须有索引;反过来也一样。没索引=每次匹配都要扫一遍右表。
实操建议:
- 检查
ON子句两侧字段:比如orders.user_id = users.id,users.id通常有主键索引,但orders.user_id很可能没有——得手动加:CREATE INDEX idx_orders_user_id ON orders(user_id) - 复合索引要按
ON+WHERE顺序排列:如果查询是JOIN ... ON a.x = b.y WHERE b.status = 'active',优先建(y, status)而不是(status, y),因为等值 JOIN 字段必须放最左 - 注意 NULL 值:如果 JOIN 字段允许 NULL,而索引列未设
NOT NULL,某些场景下索引选择率下降,优化器可能跳过它
LEFT JOIN 变成全表扫描?小心 ON 和 WHERE 混用
LEFT JOIN 的逻辑本应保留左表所有行,但一旦把右表的过滤条件写在 WHERE 里(比如 WHERE b.name LIKE '%foo%'),就等价于强制转成 INNER JOIN,还让优化器误判数据分布,常导致放弃索引、改走 Seq Scan。
实操建议:
- 右表的过滤条件一律写进
ON子句,而不是WHERE:正确写法是LEFT JOIN b ON a.id = b.a_id AND b.status = 'active' - 如果确实需要保留左表空匹配,又得筛右表,就用子查询提前聚合:
LEFT JOIN (SELECT * FROM b WHERE status = 'active') b2 ON a.id = b2.a_id - MySQL 8.0+ 和 PostgreSQL 支持 LATERAL,复杂右表计算可考虑它,但别为简单过滤强行上
索引建了还是慢?查查统计信息准不准
索引存在 ≠ 优化器会用。PostgreSQL 依赖 pg_stats,MySQL 依赖 INFORMATION_SCHEMA.STATISTICS,这些统计信息一旦过期(比如大批 INSERT/UPDATE 后没 ANALYZE),优化器就会低估/高估某张表的行数,选错执行路径——比如该走索引嵌套循环,却选了哈希连接再全表扫描。
实操建议:
- PostgreSQL 手动更新:对大表跑一次
ANALYZE table_name;生产环境可设autovacuum_analyze_scale_factor = 0.02加密采样 - MySQL 检查
information_schema.TABLES中的UPDATE_TIME和TABLE_ROWS是否明显偏离实际;必要时ANALYZE TABLE table_name - 别盲目增加索引数量:每个新增索引都会拖慢写操作,并可能干扰优化器做选择——先删掉长期不用的索引,再加新的
最常被忽略的一点:JOIN 性能问题往往不是单个技术点的问题,而是统计信息 + 索引结构 + 查询写法三者咬合失准。调一个,得同步验证另外两个。










