CodeGym /課程 /SQL SELF /函式與程序效能分析:用 EXPLAIN ANALYZE

函式與程序效能分析:用 EXPLAIN ANALYZE

SQL SELF
等級 56 , 課堂 1
開放

EXPLAIN ANALYZE 可以幫你搞懂 PostgreSQL 執行查詢時「在想什麼」:

  • 處理資料時會做哪些步驟。
  • 每個步驟花多少時間。
  • 為什麼某個查詢很慢——可能是整張表都掃過(英文叫 Seq Scan),或是 index 沒用上。

EXPLAIN ANALYZE 這個指令其實會真的執行查詢 而且 會秀給你看 PostgreSQL 怎麼優化執行過程。想像你在拆開手錶,想看裡面的機械怎麼動。EXPLAIN ANALYZE 就是在幫你拆 SQL 查詢的機械。

EXPLAIN ANALYZE 語法

先從簡單的開始。這是最基本的指令長相:

EXPLAIN ANALYZE
SELECT * FROM students WHERE age > 20;

這個查詢會執行 SELECT,然後秀出 PostgreSQL 怎麼處理資料。

EXPLAIN ANALYZE 的結果是一棵查詢執行樹。每一層都描述 PostgreSQL 做的某個步驟:

  • Operation Type — 操作類型(像 Seq ScanIndex Scan 這種)。
  • Cost — PostgreSQL 覺得這個操作「多貴」。
  • Rows — 預期會有幾列,實際結果有幾列。
  • Time — 這個步驟花了多少時間。

輸出範例:

Seq Scan on students  (cost=0.00..35.50 rows=1000 width=64) (actual time=0.023..0.045 rows=250 loops=1)
  Filter: (age > 20)
Planning Time: 0.123 ms
Execution Time: 0.234 ms

注意 Seq Scan on students。這代表 PostgreSQL 會把 students 這張表的所有資料都掃過。如果這張表很大,會超級慢。

EXPLAIN ANALYZE 實戰範例

來看幾個實戰例子,教你怎麼找出查詢的問題並解決。

範例 1:整張表都掃過

EXPLAIN ANALYZE
SELECT * FROM students WHERE age > 20;

輸出:

Seq Scan on students  (cost=0.00..35.50 rows=1000 width=64) (actual time=0.023..0.045 rows=250 loops=1)
  Filter: (age > 20)
Planning Time: 0.123 ms
Execution Time: 0.234 ms

問題在於 PostgreSQL 做了 Seq Scan,也就是整張表都掃過。如果表裡有幾百萬筆資料,這就會變成效能瓶頸。

解法:幫 age 欄位加個 index。

CREATE INDEX idx_students_age ON students(age);

現在再執行一次同樣的查詢:

EXPLAIN ANALYZE
SELECT * FROM students WHERE age > 20;

輸出:

Index Scan using idx_students_age on students  (cost=0.29..12.30 rows=250 width=64) (actual time=0.005..0.014 rows=250 loops=1)
  Index Cond: (age > 20)
Planning Time: 0.123 ms
Execution Time: 0.045 ms

你會看到 Index Scan,不是 Seq Scan 了。爽啦,查詢變超快!

範例 2:有 JOIN 的複雜查詢

假設我們有兩張表:studentscourses。我們想知道學生的名字和他們選的課程名稱。

EXPLAIN ANALYZE
SELECT s.name, c.course_name
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON e.course_id = c.id;

輸出可能長這樣:

Nested Loop  (cost=1.23..56.78 rows=500 width=128) (actual time=0.123..2.345 rows=500 loops=1)
  -> Seq Scan on students s  (cost=0.00..12.50 rows=1000 width=64) (actual time=0.023..0.045 rows=1000 loops=1)
  -> Index Scan using idx_enrollments_student_id on enrollments e  (cost=0.29..2.345 rows=3 width=64) (actual time=0.005..0.014 rows=3 loops=1000)
  -> Index Scan using idx_courses_id on courses c  (cost=0.29..2.345 rows=3 width=64) (actual time=0.005..0.014 rows=3 loops=1000)
Execution Time: 2.456 ms

你看,PostgreSQL 很聰明:有用 enrollmentscourses 的 index,查詢速度很快。如果有哪個 index 沒有,可能就會看到 Seq Scan,那就會慢下來。

函式效能優化

假設我們有個函式,會回傳大於某個年齡的學生名單:

CREATE OR REPLACE FUNCTION get_students_older_than(min_age INT)
RETURNS TABLE(id INT, name TEXT) AS $$
BEGIN
  RETURN QUERY
  SELECT id, name
  FROM students
  WHERE age > min_age;
END;
$$ LANGUAGE plpgsql;

我們可以用 EXPLAIN ANALYZE 來分析這個函式的效能:

EXPLAIN ANALYZE
SELECT * FROM get_students_older_than(20);

讓函式跑更快

如果這個函式執行很慢,通常是因為整張表都掃過。要解決這個問題:

  1. 確認你在 filter 用到的欄位(age)有加 index。
  2. 檢查表裡有多少資料,如果太多可以考慮分區(partitioning)。

瓶頸與解法

1. 整張表都掃過(Seq Scan)。 用 index 來加速查詢。但要注意,index 太多會讓 insert 變慢。

2. 查詢結果太多列。 如果查詢會回傳幾百萬筆資料,考慮加 filter(WHERELIMIT)或用分頁(OFFSET)。

3. 「很貴」的操作。 像排序、聚合或合併大表這種操作會吃很多資源。可以用 index,或把查詢拆成幾個步驟。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION