PostgreSQL 爲給它的每一個查詢產生一個查詢規劃。 爲匹配查詢結構和數據屬性選擇正確的規劃對性能絕對有關鍵性的影響。 所以系統包含了一個複雜的規劃器用於尋找最優的規劃。 你可使用 EXPLAIN 命令察看規劃器爲每一個查詢生成的查詢規劃是什麼。 閱讀查詢規劃是一門值得寫一個至關長的教程的學問, 而我這份文檔可不是這樣的教程,可是這裏有一些基本的信息。php
查詢規劃的結構是一個規劃節點的樹。 最底層的節點是表掃描節點:它們從表中返回原始數據行。 不一樣的表訪問模式有不一樣的掃描節點類型:順序掃描,索引掃描,以及位圖索引掃描。 若是查詢須要鏈接,彙集,排序,或者是對原始行的其它操做, 那麼就會在掃描節點"之上"有其它額外的節點。 而且,作這些操做一般都有多種方法,所以在這些位置也有可能出現不一樣的節點類型。 EXPLAIN 的輸出給規劃樹裏面的每一個節點都有一行輸出, 顯示基本的節點類型和規劃器爲執行這個規劃節點計算出來的預計的開銷值。 第一行(最上層的節點)是對該規劃的總的執行開銷的預計;這個數值就是規劃器試圖最小化的數值。html
這裏是一個簡單的例子,只是用來顯示輸出會有些啥: [1]sql
EXPLAIN SELECT * FROM tenk1; QUERY PLAN ------------------------------------------------------------- Seq Scan on tenk1 ()
EXPLAIN 引用的數據是:工具
預計的啓動開銷(在輸出掃描開始以前消耗的時間,也就是,在一個排序節點裏作排續的時間)。oop
預計的總開銷(若是全部的行都被檢索的話, 不過極可能不是這樣:好比帶有 LIMIT 子句的查詢將會在 Limit 規劃節點的輸入節點裏很快中止。)。性能
預計的這個規劃節點輸出的行數。 (一樣,只執行到完成爲止)。測試
預計的這個規劃節點的行的平均寬度(以字節計算)。spa
開銷是以磁盤頁面的存取爲單位計算的。也就是,定義上 1.0 等於一次順序的磁盤頁面抓取。 (同時也計算了 CPU 的開銷;它們被用一些很是隨意的捏造的權值被轉換成磁盤頁面單位。 若是你想試驗這些東西,請參閱在 Section 17.6.2 裏的運行時參數列表。)code
有一點很重要:那就是一個上層節點的開銷包括它的全部子節點的開銷。 還有一點也很重要:就是這個開銷只反映規劃器關心的東西。 尤爲是開銷沒有把結果行傳遞給客戶端的時間考慮進去, 這個時間可能在真正的總時間裏面佔據至關重要的份量; 可是被規劃器忽略了,由於它沒法經過修改規劃來改變之。 (咱們相信,每一個正確的規劃都將輸出一樣的記錄集。)orm
輸出的行數有一些小技巧,由於它不是規劃節點處理/掃描過的行數,一般會少一些, 反映對應用於此節點上的任意 WHERE 子句條件的選擇性估計。 一般而言,頂層的行預計會接近於查詢實際返回,更新,或刪除的行數。
回到咱們的例子:
EXPLAIN SELECT * FROM tenk1; QUERY PLAN ------------------------------------------------------------- Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
這個例子就象例子自己同樣直接了當。若是你作一個
SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';
你會發現 tenk1 有 358 磁盤頁面和 10000 行。 所以開銷計算爲 358 次頁面讀取,定義爲每塊 1.0, 加上 10000 * cpu_tuple_cost,一般是 0.01(用命令 SHOW cpu_tuple_cost 查看)。
如今讓咱們修改查詢並增長一個WHERE條件:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 7000; QUERY PLAN ------------------------------------------------------------ Seq Scan on tenk1 (cost=0.00..483.00 rows=7033 width=244) Filter: (unique1 < 7000)
請注意 EXPLAIN 輸出顯示 WHERE 子句看成一個 "filter" 應用; 這意味着規劃節點爲它掃描的每一行檢查該條件,而且只輸出經過條件的行。 預計的輸出行數下降了,由於有WHERE子句。 不過,掃描仍將必須訪問全部 10000 行,所以開銷沒有下降; 實際上它還增長了一些以反映檢查WHERE條件的額外 CPU 時間。
這條查詢實際選擇的行數是 7000,可是預計的數目只是個大概。 若是你試圖重複這個試驗,那麼你極可能獲得有些不一樣的預計; 還有,這個預計會在每次 ANALYZE 命令以後改變, 由於 ANALYZE 生成的統計是從該表中隨機抽取的樣本計算的。
把查詢限制條件改得更嚴格:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100; QUERY PLAN ------------------------------------------------------------------------------ Bitmap Heap Scan on tenk1 (cost=2.37..232.35 rows=106 width=244) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..2.37 rows=106 width=0) Index Cond: (unique1 < 100)
這裏,規劃器決定使用兩步的規劃:最底層的規劃節點訪問一個索引,找出匹配索引條件的行的位置, 而後上層規劃節點真實地從表裏面抓取出那些行。獨立地抓取數據行比順序地讀取它們的開銷高不少, 可是由於並不是全部表的頁面都被訪問了,這麼作實際上仍然比一次順序掃描開銷要少。 (使用兩層規劃的緣由是由於上層規劃節點把索引標識出來的行位置在讀取它們以前按照物理位置排序, 這樣能夠最小化獨立抓取的開銷。節點名稱裏面提到的"位圖(bitmap)"是進行排序的機制。
若是 WHERE 條件有足夠的選擇性,規劃器可能會切換到一個"簡單的"索引掃描規劃:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 3; QUERY PLAN ------------------------------------------------------------------------------ Index Scan using tenk1_unique1 on tenk1 (cost=0.00..10.00 rows=2 width=244) Index Cond: (unique1 < 3)
在這個例子理,表的數據行是以索引順序抓取的,這樣就令讀取它們的開銷更大, 可是這裏的行少得可憐,所以額外的行位置的排序並不值得。你最多見的就是看到這種規劃類型只抓取一行, 以及是那些要求一個 ORDER BY 條件匹配索引順序的查詢。
向WHERE子句裏面增長另一個條件:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 3 AND stringu1 = 'xxx'; QUERY PLAN ------------------------------------------------------------------------------ Index Scan using tenk1_unique1 on tenk1 (cost=0.00..10.01 rows=1 width=244) Index Cond: (unique1 < 3) Filter: (stringu1 = 'xxx'::name)
新增的條件 stringu1 = 'xxx' 減小了預計的輸出行, 可是沒有減小開銷,由於咱們仍然須要訪問相同的行。 請注意 stringu1 子句不能當作一個索引條件施用 (由於這個索引只是在unique1 列上有)。 它是當作一個從索引中檢索出的行的過濾器來用的。 所以開銷實際上略微增長了一些以反映這個額外的檢查。
若是在 WHERE 裏面使用的好幾個字段上有索引, 那麼規劃器可能會使用索引的 AND 或者 OR 的組合:
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000; QUERY PLAN ------------------------------------------------------------------------------------- Bitmap Heap Scan on tenk1 (cost=11.27..49.11 rows=11 width=244) Recheck Cond: ((unique1 < 100) AND (unique2 > 9000)) -> BitmapAnd (cost=11.27..11.27 rows=11 width=0) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..2.37 rows=106 width=0) Index Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique2 (cost=0.00..8.65 rows=1042 width=0) Index Cond: (unique2 > 9000)
可是這麼作要求訪問兩個索引,所以與只使用一個索引,而把另一個條件只看成過濾器相比,這個方法未必是更優。 若是你改變涉及的範圍,你會看到規劃器相應地發生變化。
讓咱們試着使用咱們上面討論的字段鏈接兩個表:
EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2; QUERY PLAN -------------------------------------------------------------------------------------- Nested Loop (cost=2.37..553.11 rows=106 width=488) -> Bitmap Heap Scan on tenk1 t1 (cost=2.37..232.35 rows=106 width=244) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..2.37 rows=106 width=0) Index Cond: (unique1 < 100) -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.00..3.01 rows=1 width=244) Index Cond: ("outer".unique2 = t2.unique2)
在這個嵌套循環裏,外層的掃描是和咱們前面看到的一樣的位圖索引, 所以其開銷和行計數是同樣的,由於咱們在該節點上附加了 WHERE 子句 unique1 < 100。 這個時候 t1.unique2 = t2.unique2 子句尚未什麼關係, 所以它不影響外層掃描的行計數。對於內層掃描,當前外層掃描的數據行的 unique2 被插入內層索引掃描生成相似 t2.unique2 = constant 這樣的索引條件。所以,咱們拿到和從 EXPLAIN SELECT * FROM tenk2 WHERE unique2 = 42 那邊拿到的同樣的內層掃描計劃和開銷。而後,之外層掃描的開銷爲基礎設置循環節點的開銷, 加上每一個外層行的一個重複(這裏是 106 * 3.01),而後再加上鍊接處理須要的一點點 CPU 時間。
在這個例子裏,鏈接的輸出行數與兩個掃描的行數的乘積相同, 可是一般並非這樣的,由於一般你會有說起兩個表的WHERE子句, 所以它只能應用於鏈接(join)點,而不能影響兩個關係的輸入掃描。 好比,若是咱們加一條 WHERE ... AND t1.hundred < t2.hundred, 將減小輸出行數,可是不改變任何一個輸入掃描。
尋找另一個規劃的方法是經過設置每種規劃類型的容許/禁止開關, (在 Section 17.6.1 裏描述) 強制規劃器拋棄它認爲優秀的(掃描)策略。 (這個工具目前比較原始,但頗有用。又見Section 13.3。)
SET enable_nestloop = off; EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2; QUERY PLAN ------------------------------------------------------------------------------------------ Hash Join (cost=232.61..741.67 rows=106 width=488) Hash Cond: ("outer".unique2 = "inner".unique2) -> Seq Scan on tenk2 t2 (cost=0.00..458.00 rows=10000 width=244) -> Hash (cost=232.35..232.35 rows=106 width=244) -> Bitmap Heap Scan on tenk1 t1 (cost=2.37..232.35 rows=106 width=244) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..2.37 rows=106 width=0) Index Cond: (unique1 < 100)
這個規劃仍然試圖用一樣的索引掃描從tenk1 裏面取出感興趣的 100 行, 把它們藏在一個在內存裏的散列(哈希)表裏,而後對 tenk2 作一次順序掃描,對每一條tenk2記錄檢測上面的散列(哈希)表, 尋找可能匹配t1.unique2 = t2.unique2 的行。 讀取tenk1和創建散列表是此散列聯接的所有啓動開銷, 由於咱們在開始讀取tenk2 以前不可能得到任何輸出行。 這個聯接的總的預計時間一樣還包括至關重的檢測散列(哈希)表 10000 次的 CPU 時間。不過,請注意,咱們不須要對 232.35 乘 10000; 散列(哈希)表的在這個規劃類型中只須要設置一次。
咱們能夠用EXPLAIN ANALYZE檢查規劃器的估計值的準確性。 這個命令實際上執行該查詢而後顯示每一個規劃節點內實際運行時間的和以及單純EXPLAIN顯示的估計開銷。 好比,咱們能夠象下面這樣獲取一個結果:
EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Nested Loop (cost=2.37..553.11 rows=106 width=488) (actual time=1.392..12.700 rows=100 loops=1) -> Bitmap Heap Scan on tenk1 t1 (cost=2.37..232.35 rows=106 width=244) (actual time=0.878..2.367 rows=100 loops=1) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..2.37 rows=106 width=0) (actual time=0.546..0.546 rows=100 loops=1) Index Cond: (unique1 < 100) -> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.00..3.01 rows=1 width=244) (actual time=0.067..0.078 rows=1 loops=100) Index Cond: ("outer".unique2 = t2.unique2) Total runtime: 14.452 ms
請注意 "actual time" 數值是以真實時間的毫秒計的, 而 "cost" 估計值是以任意磁盤抓取的單元計的; 所以它們極可能不一致。咱們要關心的事是兩組比值是否一致。
在一些查詢規劃裏,一個子規劃節點極可能運行屢次。 好比,在上面的嵌套循環的規劃裏,內層的索引掃描是對每一個外層行執行一次的。 在這種狀況下,"loops" 報告該節點執行的總數目, 而顯示的實際時間和行數目是每次執行的平均值。 這麼作的緣由是令這些數字與開銷預計顯示的數字具備可比性。 要乘以 "loops" 值才能得到在該節點時間花費的總時間。
EXPLAIN ANALYZE 顯示的 "Total runtime" 包括執行器啓動和關閉的時間, 以及花在處理結果行上的時間。它不包括分析,重寫,或者規劃的時間。 對於SELECT查詢,總運行時間一般只是比從頂層規劃節點彙報出來的總時間略微大些。 對於INSERT,UPDATE,和 DELETE 查詢, 總運行時間可能會顯著增大,由於它包括花費在處理結果行上的時間。 在這些查詢裏,頂層規劃節點的時間其實是花在計算新行和/或定位舊行上的時間,可是不包括花在標記變化上的時間。
若是EXPLAIN的結果除了在你實際測試的狀況以外不能推導出其它的狀況, 那它就什麼用都沒有;好比,在一個小得象玩具的表上的結果不能適用於大表。 規劃器的開銷計算不是線性的,所以它極可能對大些或者小些的表選擇不一樣的規劃。 一個極端的例子是一個只佔據一個磁盤頁面的表,在這樣的表上,無論它有沒有索引可使用, 你幾乎都老是獲得順序掃描規劃。規劃器知道無論在任何狀況下它都要進行一個磁盤頁面的讀取, 因此再擴大幾個磁盤頁面讀取以查找索引是沒有意義的