今天我們要搞懂 PostgreSQL 執行計劃裡的節點是什麼、怎麼看,還有最重要的——怎麼判斷哪裡怪怪的。你會知道為什麼資料庫有時候硬要用超吃資源的 Seq Scan,明明你都建好 index 了,甚至不只一個。
當 PostgreSQL 在建立查詢執行計劃時,它會把查詢拆成一個個步驟,這些步驟就叫做節點。每個節點就像資料庫伺服器處理你查詢時的一個「步驟」。主要的節點類型有:
Sequential Scan(Seq Scan)
Seq Scan,也就是「順序掃描」,是最簡單的從資料表撈資料的方式。PostgreSQL 會直接把整個表一行一行讀過去,然後檢查每一行有沒有符合你的查詢條件。
什麼時候會用 Seq Scan?
Seq Scan 會在這些情況下出現:
- 資料表沒有適合的 index 可以加速查詢。
- 過濾條件太寬泛,index 幫不上忙(像是要撈超過 50% 的資料)。
- PostgreSQL 覺得直接順序讀表會比用 index 還快(有時候小表真的會這樣)。
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 就是用 index 來掃資料。你在 PostgreSQL 建 index,其實就像幫資料表做個「目錄」。如果查詢能用到 index,PostgreSQL 就只會去找需要的那幾行,不用整張表都翻。
什麼時候會用 Index Scan?
- 查詢有針對有 index 的欄位加過濾條件(像
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。原本要一行一行翻的,現在直接用 index 快速定位。
Index Scan 的優點:
- 大表查詢速度大幅提升。
- 減少從硬碟讀資料的量。
Index Scan 的問題:
如果查詢要撈太多資料(像超過一半的表),用 index 反而可能比 Seq Scan 還慢。
Hash Join
Hash Join 是用來把兩張表根據 join 條件(像 ON students.course_id = courses.id)合在一起的。PostgreSQL 會先幫其中一張(通常比較小)建一個 hash table,然後用這個 hash table 去另一張表找對應的資料。
什麼時候會用 Hash Join?
- 用
INNER JOIN、LEFT JOIN等等把表 join 起來時。 - PostgreSQL 覺得
Hash Join比其他 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 就是把兩張表 join 起來。注意 PostgreSQL 會先對兩張表都做 Seq Scan,然後再建 hash table(Hash)。
Hash Join 的優點:
- 中型表 join 起來很快。
- 適合 join 很多資料的情境。
Hash Join 的問題:
如果 hash table 太大超過記憶體,PostgreSQL 會把它寫到硬碟,這樣 join 就會變超慢。
執行計劃分析範例
來看一個實際的例子。
查詢:
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兩張表 join 起來。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表建 hash table。
節點比較與選擇
在分析執行計劃時,重點是要懂 PostgreSQL 為什麼選這種資料處理方式。有時候你得自己動手優化,比如加 index 或重寫查詢。給你幾個小建議:
- 如果看到大表用
Seq Scan,考慮加 index。 Hash Join太慢時,檢查 PostgreSQL 的記憶體設定。- 用
EXPLAIN ANALYZE比較預估和實際的指標(rows、time)。
到這裡你已經有基本概念,知道怎麼看查詢執行計劃和解讀節點了。接下來的課我們會聊常見的優化問題和解法。
GO TO FULL VERSION