http://toplchx.iteye.com/blog/2091860
使用EXPLAIN
PostgreSQL為每個收到的查詢設計一個查詢規划。選擇正確的匹配查詢結構和數據屬性的規划對執行效率是至關重要要的,所以系統包含一個復雜的規划器來試圖選擇好的規划。你可以使用EXPLAIN命令查看查詢規划器創建的任何查詢。閱讀查詢規划是一門藝術,需要掌握一定的經驗,本節試圖涵蓋一些基礎知識。
以下的例子來自PostgreSQL 9.3開發版。
EXPLAIN基礎
查詢規划是以規划為節點的樹形結構。樹的最底節點是掃描節點:他返回表中的原數據行。
不同的表有不同的掃描節點類型:順序掃描,索引掃描和位圖索引掃描。
也有非表列源,如VALUES子句並設置FROM返回,他們有自己的掃描類型。
如果查詢需要關聯,聚合,排序或其他操作,會在掃描節點之上增加節點執行這些操作。通常有不只一種可能的方式做這些操作,所以可能出現不同的節點類型。
EXPLAIN的輸出是每個樹節點顯示一行,內容是基本節點類型和執行節點的消耗評估。可能會楚翔其他行,從匯總行節點縮進顯示節點的其他屬性。第一行(最上節點的匯總行)是評估執行計划的總消耗,這個值越小越好。
下面是一個簡單的例子:
- EXPLAIN SELECT * FROM tenk1;
- QUERY PLAN
- -------------------------------------------------------------
- Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
- 評估開始消耗。這是可以開始輸出前的時間,比如排序節點的排序的時間。
- 評估總消耗。假設查詢從執行到結束的時間。有時父節點可能停止這個過程,比如LIMIT子句。
- 評估查詢節點的輸出行數,假設該節點執行結束。
- 評估查詢節點的輸出行的平均字節數。
這個消耗的計算依賴於規划器的設置參數,這里的例子都是在默認參數下運行。
需要知道的是:上級節點的消耗包括其子節點的消耗。這個消耗值只反映規划器關心的內容,一般這個消耗不包括將數據傳輸到客戶端的時間。
評估的行數不是執行和掃描查詢節點的數量,而是節點返回的數量。它通常會少於掃描數量,因為有WHERE條件會過濾掉一些數據。理想情況頂級行數評估近似於實際返回的數量
回到剛才的例子,表tenk1有10000條數據分布在358個磁盤頁,評估時間是(磁盤頁*seq_page_cost)+(掃描行*cpu_tuple_cost)。默認seq_page_cost是1.0,cpu_tuple_cost是0.01,所以評估值是(358 * 1.0) + (10000 * 0.01) = 458
現在我們將查詢加上WHERE子句:
- EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 7000;
- QUERY PLAN
- ------------------------------------------------------------
- Seq Scan on tenk1 (cost=0.00..483.00 rows=7001 width=244)
- Filter: (unique1 < 7000)
查詢節點增加了“filter”條件。這意味着查詢節點為掃描的每一行數據增加條件檢查,只輸入符合條件數據。評估的輸出記錄數因為where子句變少了,但是掃描的數據還是10000條,所以消耗沒有減少,反而增加了一點cup的計算時間。
這個查詢實際輸出的記錄數是7000,但是評估是個近似值,多次運行可能略有差別,這中情況可以通過ANALYZE命令改善。
現在再修改一下條件
- EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100;
- QUERY PLAN
- ------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1 (cost=5.07..229.20 rows=101 width=244)
- Recheck Cond: (unique1 < 100)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0)
- Index Cond: (unique1 < 100)
現在,增加另一個查詢條件:
- EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND stringu1 = 'xxx';
- QUERY PLAN
- ------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1 (cost=5.04..229.43 rows=1 width=244)
- Recheck Cond: (unique1 < 100)
- Filter: (stringu1 = 'xxx'::name)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0)
- Index Cond: (unique1 < 100)
如果在不同的字段上有獨立的索引,規划器可能選擇使用AND或者OR組合索引:
- EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000;
- QUERY PLAN
- -------------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1 (cost=25.08..60.21 rows=10 width=244)
- Recheck Cond: ((unique1 < 100) AND (unique2 > 9000))
- -> BitmapAnd (cost=25.08..25.08 rows=10 width=0)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0)
- Index Cond: (unique1 < 100)
- -> Bitmap Index Scan on tenk1_unique2 (cost=0.00..19.78 rows=999 width=0)
- Index Cond: (unique2 > 9000)
下面我們來看看LIMIT的影響:
- EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
- QUERY PLAN
- -------------------------------------------------------------------------------------
- Limit (cost=0.29..14.48 rows=2 width=244)
- -> Index Scan using tenk1_unique2 on tenk1 (cost=0.29..71.27 rows=10 width=244)
- Index Cond: (unique2 > 9000)
- Filter: (unique1 < 100)
來看一下通過索引字段的表連接:
- EXPLAIN SELECT *
- FROM tenk1 t1, tenk2 t2
- WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2;
- QUERY PLAN
- --------------------------------------------------------------------------------------
- Nested Loop (cost=4.65..118.62 rows=10 width=488)
- -> Bitmap Heap Scan on tenk1 t1 (cost=4.36..39.47 rows=10 width=244)
- Recheck Cond: (unique1 < 10)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.36 rows=10 width=0)
- Index Cond: (unique1 < 10)
- -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.29..7.91 rows=1 width=244)
- Index Cond: (unique2 = t1.unique2)
外部節點的消耗加上循環內部節點的消耗(39.47+10*7.91)再加一點CPU時間就得到規划的總消耗。
再看一個例子:
- EXPLAIN SELECT *
- FROM tenk1 t1, tenk2 t2
- WHERE t1.unique1 < 10 AND t2.unique2 < 10 AND t1.hundred < t2.hundred;
- QUERY PLAN
- ---------------------------------------------------------------------------------------------
- Nested Loop (cost=4.65..49.46 rows=33 width=488)
- Join Filter: (t1.hundred < t2.hundred)
- -> Bitmap Heap Scan on tenk1 t1 (cost=4.36..39.47 rows=10 width=244)
- Recheck Cond: (unique1 < 10)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.36 rows=10 width=0)
- Index Cond: (unique1 < 10)
- -> Materialize (cost=0.29..8.51 rows=10 width=244)
- -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.29..8.46 rows=10 width=244)
- Index Cond: (unique2 < 10)
注意這次規划器選擇使用Meaterialize節點,將條件加入內部節點,這以為着內部節點的索引掃描只做一次,即使嵌套循環需要讀取這些數據10次,Meterialize節點將數據保存在內存中,每次循環都從內存中讀取數據。
如果我們稍微改變一下查詢,會看到完全不同的規划:
- EXPLAIN SELECT *
- FROM tenk1 t1, tenk2 t2
- WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;
- QUERY PLAN
- ------------------------------------------------------------------------------------------
- Hash Join (cost=230.47..713.98 rows=101 width=488)
- Hash Cond: (t2.unique2 = t1.unique2)
- -> Seq Scan on tenk2 t2 (cost=0.00..445.00 rows=10000 width=244)
- -> Hash (cost=229.20..229.20 rows=101 width=244)
- -> Bitmap Heap Scan on tenk1 t1 (cost=5.07..229.20 rows=101 width=244)
- Recheck Cond: (unique1 < 100)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0)
- Index Cond: (unique1 < 100)
注意縮進反應的規划結構。在tenk1表上的bitmap掃描結果作為Hash節點的輸入建立哈希表。然后Hash Join節點讀取外層子節點的數據,再循環檢索哈希表的數據。
另一個可能的連接類型是merge join:
- EXPLAIN SELECT *
- FROM tenk1 t1, onek t2
- WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;
- QUERY PLAN
- ------------------------------------------------------------------------------------------
- Merge Join (cost=198.11..268.19 rows=10 width=488)
- Merge Cond: (t1.unique2 = t2.unique2)
- -> Index Scan using tenk1_unique2 on tenk1 t1 (cost=0.29..656.28 rows=101 width=244)
- Filter: (unique1 < 100)
- -> Sort (cost=197.83..200.33 rows=1000 width=244)
- Sort Key: t2.unique2
- -> Seq Scan on onek t2 (cost=0.00..148.00 rows=1000 width=244)
有一種方法可以看到不同的規划,就是強制規划器忽略任何策略。例如,如果我們不相信排序順序掃描(sequential-scan-and-sort)是最好的辦法,我們可以嘗試這樣的做法:
- SET enable_sort = off;
- EXPLAIN SELECT *
- FROM tenk1 t1, onek t2
- WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;
- QUERY PLAN
- ------------------------------------------------------------------------------------------
- Merge Join (cost=0.56..292.65 rows=10 width=488)
- Merge Cond: (t1.unique2 = t2.unique2)
- -> Index Scan using tenk1_unique2 on tenk1 t1 (cost=0.29..656.28 rows=101 width=244)
- Filter: (unique1 < 100)
- -> Index Scan using onek_unique2 on onek t2 (cost=0.28..224.79 rows=1000 width=244)
EXPLAIN ANALYZE
通過EXPLAIN ANALYZE可以檢查規划器評估的准確性。使用ANALYZE選項,EXPLAIN實際運行查詢,顯示真實的返回記錄數和運行每個規划節點的時間,例如我們可以得到下面的結果:
- EXPLAIN ANALYZE SELECT *
- FROM tenk1 t1, tenk2 t2
- WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2;
- QUERY PLAN
- ---------------------------------------------------------------------------------------------------------------------------------
- Nested Loop (cost=4.65..118.62 rows=10 width=488) (actual time=0.128..0.377 rows=10 loops=1)
- -> Bitmap Heap Scan on tenk1 t1 (cost=4.36..39.47 rows=10 width=244) (actual time=0.057..0.121 rows=10 loops=1)
- Recheck Cond: (unique1 < 10)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.36 rows=10 width=0) (actual time=0.024..0.024 rows=10 loops=1)
- Index Cond: (unique1 < 10)
- -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.29..7.91 rows=1 width=244) (actual time=0.021..0.022 rows=1 loops=10)
- Index Cond: (unique2 = t1.unique2)
- Total runtime: 0.501 ms
通常最重要的是看評估的記錄數是否和實際得到的記錄數接近。在這個例子里評估數完全和實際一樣,但這種情況很少出現。
某些查詢規划可能執行多次子規划。比如之前提過的內循環規划(nested-loop),內部索引掃描的次數是外部數據的數量。在這種情況下,報告顯示循環執行的總次數、平均實際執行時間和數據條數。這樣做是為了和評估值表示方式一至。由循環次數和平均值相乘得到總消耗時間。
某些情況EXPLAIN ANALYZE會顯示額外的信息,比如sort和hash節點的時候:
- EXPLAIN ANALYZE SELECT *
- FROM tenk1 t1, tenk2 t2
- WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;
- QUERY PLAN
- --------------------------------------------------------------------------------------------------------------------------------------------
- Sort (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1)
- Sort Key: t1.fivethous
- Sort Method: quicksort Memory: 77kB
- -> Hash Join (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1)
- Hash Cond: (t2.unique2 = t1.unique2)
- -> Seq Scan on tenk2 t2 (cost=0.00..445.00 rows=10000 width=244) (actual time=0.007..2.583 rows=10000 loops=1)
- -> Hash (cost=229.20..229.20 rows=101 width=244) (actual time=0.659..0.659 rows=100 loops=1)
- Buckets: 1024 Batches: 1 Memory Usage: 28kB
- -> Bitmap Heap Scan on tenk1 t1 (cost=5.07..229.20 rows=101 width=244) (actual time=0.080..0.526 rows=100 loops=1)
- Recheck Cond: (unique1 < 100)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) (actual time=0.049..0.049 rows=100 loops=1)
- Index Cond: (unique1 < 100)
- Total runtime: 8.008 ms
另一種額外信息是過濾條件過濾掉的記錄數:
- EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE ten < 7;
- QUERY PLAN
- ---------------------------------------------------------------------------------------------------------
- Seq Scan on tenk1 (cost=0.00..483.00 rows=7000 width=244) (actual time=0.016..5.107 rows=7000 loops=1)
- Filter: (ten < 7)
- Rows Removed by Filter: 3000
- Total runtime: 5.905 ms
類似條件過濾的情況也會在"lossy"索引掃描時發生,比如這樣一個查詢,一個多邊形含有的特定的點:
- EXPLAIN ANALYZE SELECT * FROM polygon_tbl WHERE f1 @> polygon '(0.5,2.0)';
- QUERY PLAN
- ------------------------------------------------------------------------------------------------------
- Seq Scan on polygon_tbl (cost=0.00..1.05 rows=1 width=32) (actual time=0.044..0.044 rows=0 loops=1)
- Filter: (f1 @> '((0.5,2))'::polygon)
- Rows Removed by Filter: 4
- Total runtime: 0.083 ms
但是,如果我們強制使用索引掃描,將會看到:
- SET enable_seqscan TO off;
- EXPLAIN ANALYZE SELECT * FROM polygon_tbl WHERE f1 @> polygon '(0.5,2.0)';
- QUERY PLAN
- --------------------------------------------------------------------------------------------------------------------------
- Index Scan using gpolygonind on polygon_tbl (cost=0.13..8.15 rows=1 width=32) (actual time=0.062..0.062 rows=0 loops=1)
- Index Cond: (f1 @> '((0.5,2))'::polygon)
- Rows Removed by Index Recheck: 1
- Total runtime: 0.144 ms
EXPLAIN還有BUFFERS選項可以和ANALYZE一起使用,來得到更多的運行時間分析:
- EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000;
- QUERY PLAN
- ---------------------------------------------------------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1 (cost=25.08..60.21 rows=10 width=244) (actual time=0.323..0.342 rows=10 loops=1)
- Recheck Cond: ((unique1 < 100) AND (unique2 > 9000))
- Buffers: shared hit=15
- -> BitmapAnd (cost=25.08..25.08 rows=10 width=0) (actual time=0.309..0.309 rows=0 loops=1)
- Buffers: shared hit=7
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) (actual time=0.043..0.043 rows=100 loops=1)
- Index Cond: (unique1 < 100)
- Buffers: shared hit=2
- -> Bitmap Index Scan on tenk1_unique2 (cost=0.00..19.78 rows=999 width=0) (actual time=0.227..0.227 rows=999 loops=1)
- Index Cond: (unique2 > 9000)
- Buffers: shared hit=5
- Total runtime: 0.423 ms
Buffers提供的數據可以幫助確定哪些查詢是I/O密集型的。
請注意EXPLAIN ANALYZE實際運行查詢,任何實際影響都會發生。如果要分析一個修改數據的查詢又不想改變你的表,你可以使用roll back命令進行回滾,比如:
- BEGIN;
- EXPLAIN ANALYZE UPDATE tenk1 SET hundred = hundred + 1 WHERE unique1 < 100;
- QUERY PLAN
- --------------------------------------------------------------------------------------------------------------------------------
- Update on tenk1 (cost=5.07..229.46 rows=101 width=250) (actual time=14.628..14.628 rows=0 loops=1)
- -> Bitmap Heap Scan on tenk1 (cost=5.07..229.46 rows=101 width=250) (actual time=0.101..0.439 rows=100 loops=1)
- Recheck Cond: (unique1 < 100)
- -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) (actual time=0.043..0.043 rows=100 loops=1)
- Index Cond: (unique1 < 100)
- Total runtime: 14.727 ms
- ROLLBACK;
EXPLAIN ANALYZE的"Total runtime"包括執行啟動和關閉時間,以及運行被激發的任何處觸發器的時間,但不包括分析、重寫或規划時間。執行時間包括BEFORE觸發器,但不包括AFTER觸發器,因為AFTER是在查詢運行結束之后才觸發的。每個觸發器(無論BEFORE還是AFTER)的時間也會單獨顯示出來。注意,延遲的觸發器在事務結束前都不會被執行,所以EXPLAIN ANALYZE不會顯示。

