今天我们来聊聊PostgreSQL执行计划里的节点到底是啥,怎么读懂它们,还有最重要的——怎么判断哪里出问题了。你会明白为啥你的数据库有时候会用很吃资源的Seq Scan,明明你已经建了索引,甚至不止一个。
PostgreSQL在生成查询执行计划的时候,会把它拆成几个阶段,每个阶段叫一个节点。每个节点其实就是数据库服务器处理你查询时的一个“小步骤”。主要的节点类型有:
顺序扫描(Seq Scan)
Seq Scan,也就是顺序扫描,是最简单的从表里取数据的方式。PostgreSQL会直接把整张表一行一行地读,然后检查每行是不是符合你的查询条件。
什么时候用Seq Scan?
Seq Scan会在这些情况下出现:
- 表里没有合适的索引能加速查询。
- 过滤条件太宽泛,索引用起来没啥用(比如你要查超过50%的数据)。
- PostgreSQL觉得顺序读表比用索引还快(有时候表很小就会这样)。
EXPLAIN SELECT * FROM students WHERE age > 18;
结果示例:
Seq Scan on students (cost=0.00..35.50 rows=10 width=50)
Filter: (age > 18)
注意Seq Scan on students——这就是PostgreSQL告诉你它会把“students”这张表全都读一遍。
Seq Scan的问题:如果表很大,顺序扫描会超级慢。
Index Scan
Index Scan就是用索引来查数据。当你在PostgreSQL里建了索引,其实就像给表做了个“目录”。如果查询能用上索引,PostgreSQL就不用全表扫描了,只查需要的那部分。
什么时候用Index Scan?
- 查询里有针对带索引的列的过滤条件(比如
WHERE)。 - 用了比较操作,比如
=、<、>、BETWEEN等等。
CREATE INDEX idx_students_age ON students(age);
EXPLAIN SELECT * FROM students WHERE age = 18;
结果示例:
Index Scan using idx_students_age on students (cost=0.15..8.27 rows=1 width=50)
Index Cond: (age = 18)
这里Index Scan using idx_students_age说明PostgreSQL用上了idx_students_age这个索引。原本一行一行读表,现在直接用索引,速度快多了。
Index Scan的优点:
- 大表查询能快很多。
- 减少了从磁盘读取的数据量。
Index Scan的问题:
如果你的查询返回的数据太多(比如超过一半的表),用索引反而可能比Seq Scan还慢。
Hash Join
Hash Join是用来把两张表连起来的(比如ON students.course_id = courses.id)。PostgreSQL会先给其中一张(通常是小的那张)建个哈希表,然后用它去另一张表里找匹配的行。
什么时候用Hash Join?
- 用
INNER JOIN、LEFT JOIN等连表的时候。 - PostgreSQL觉得
Hash Join比其他连表方式更高效的时候。
EXPLAIN
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id;
结果示例:
Hash Join (cost=25.00..50.00 rows=10 width=100)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..20.00 rows=10 width=50)
-> Hash (cost=15.00..15.00 rows=10 width=50)
-> Seq Scan on courses (cost=0.00..15.00 rows=10 width=50)
这里Hash Join把两张表连起来。注意PostgreSQL会先对两张表都做Seq Scan,然后再建哈希表(Hash)。
Hash Join的优点:
- 中等大小的表连表很快。
- 适合连很多行的表。
Hash Join的问题:
如果哈希表太大,内存装不下,PostgreSQL就会用磁盘,速度会慢很多。
执行计划分析示例
来看看一个实际例子。
查询:
EXPLAIN ANALYZE
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id
WHERE students.age > 18;
结果:
Hash Join (cost=35.00..75.00 rows=5 width=100) (actual time=1.00..2.50 rows=5 loops=1)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..40.00 rows=10 width=50) (actual time=0.50..1.00 rows=7 loops=1)
Filter: (age > 18)
Rows Removed by Filter: 3
-> Hash (cost=25.00..25.00 rows=5 width=50) (actual time=0.30..0.30 rows=5 loops=1)
-> Seq Scan on courses (cost=0.00..20.00 rows=5 width=50) (actual time=0.20..0.25 rows=5 loops=1)
Planning Time: 0.50 ms
Execution Time: 3.00 ms
解读:
Hash Join:主节点。PostgreSQL把students和courses连起来。actual time:1.00到2.50毫秒。rows=5:查询返回了5行。
- 子节点:
Seq Scan on students:顺序读students表,并且加了(age > 18)的过滤。Rows Removed by Filter = 3:有3行被过滤掉了。Hash:PostgreSQL给courses表建了哈希表。
节点对比和选择
分析执行计划时,关键是要明白为啥PostgreSQL选了这种处理方式。有时候你得手动干预,比如加索引或者改写查询。给你几个建议:
- 如果看到大表用
Seq Scan,考虑加索引。 Hash Join太慢的话,看看PostgreSQL的可用内存够不够。- 用
EXPLAIN ANALYZE对比预估和实际的指标(rows、time)。
现在你已经有了读懂查询执行计划和节点的基础。后面的课我们会聊聊常见的优化问题和怎么搞定它们。
GO TO FULL VERSION