CodeGym /课程 /SQL SELF /执行计划解读:如何看懂和分析节点(`Seq Scan`、`Index Scan`、`Hash Join`)

执行计划解读:如何看懂和分析节点(`Seq Scan`、`Index Scan`、`Hash Join`)

SQL SELF
第 41 级 , 课程 4
可用

今天我们来聊聊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 JOINLEFT 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

解读:

  1. Hash Join:主节点。PostgreSQL把studentscourses连起来。
    • actual time:1.00到2.50毫秒。
    • rows=5:查询返回了5行。
  2. 子节点:
    • Seq Scan on students:顺序读students表,并且加了(age > 18)的过滤。
    • Rows Removed by Filter = 3:有3行被过滤掉了。
    • Hash:PostgreSQL给courses表建了哈希表。

节点对比和选择

分析执行计划时,关键是要明白为啥PostgreSQL选了这种处理方式。有时候你得手动干预,比如加索引或者改写查询。给你几个建议:

  • 如果看到大表用Seq Scan,考虑加索引。
  • Hash Join太慢的话,看看PostgreSQL的可用内存够不够。
  • EXPLAIN ANALYZE对比预估和实际的指标(rowstime)。

现在你已经有了读懂查询执行计划和节点的基础。后面的课我们会聊聊常见的优化问题和怎么搞定它们。

1
调查/小测验
查询执行计划第 41 级,课程 4
不可用
查询执行计划
查询执行计划
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION