1、使用EXPLAIN:
PostgreSQL爲每一個查詢都生成一個查詢規劃,由於選擇正確的查詢路徑對性能的影響是極爲關鍵的。PostgreSQL自己已經包含了一個規劃器用於尋找最優規劃,咱們能夠經過使用EXPLAIN命令來查看規劃器爲每一個查詢生成的查詢規劃。
PostgreSQL中生成的查詢規劃是由1到n個規劃節點構成的規劃樹,其中最底層的節點爲表掃描節點,用於從數據表中返回檢索出的數據行。然而,不 同的掃描節點類型表明着不一樣的表訪問模式,如:順序掃描、索引掃描,以及位圖索引掃描等。若是查詢仍然須要鏈接、彙集、排序,或者是對原始行的其它操做, 那麼就會在掃描節點"之上"有其它額外的節點。而且這些操做一般都有多種方法,所以在這些位置也有可能出現不一樣的節點類型。EXPLAIN將爲規劃樹中的 每一個節點都輸出一行信息,顯示基本的節點類型和規劃器爲執行這個規劃節點計算出的預計開銷值。第一行(最上層的節點)是對該規劃的總執行開銷的預計,這個 數值就是規劃器試圖最小化的數值。
這裏有一個簡單的例子,以下:
EXPLAIN SELECT * FROM tenk1;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
EXPLAIN引用的數據是:
1). 預計的啓動開銷(在輸出掃描開始以前消耗的時間,好比在一個排序節點裏作排續的時間)。
2). 預計的總開銷。
3). 預計的該規劃節點輸出的行數。
4). 預計的該規劃節點的行平均寬度(單位:字節)。
這裏開銷(cost)的計算單位是磁盤頁面的存取數量,如1.0將表示一次順序的磁盤頁面讀取。其中上層節點的開銷將包括其全部子節點的開銷。這裏的輸 出行數(rows)並非規劃節點處理/掃描的行數,一般會更少一些。通常而言,頂層的行預計數量會更接近於查詢實際返回的行數。
如今咱們執行下面基於系統表的查詢:
SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';
從查詢結果中能夠看出tenk1表佔有358個磁盤頁面和10000條記錄,然而爲了計算cost的值,咱們仍然須要知道另一個系統參數值。
postgres=# show cpu_tuple_cost;
cpu_tuple_cost
----------------
0.01
(1 row)
cost = 358(磁盤頁面數) + 10000(行數) * 0.01(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行數據,所以開銷並無 真正下降,實際上它還增長了一些因數據過濾而產生的額外CPU開銷。
上面的數據只是一個預計數字,即便是在每次執行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)
在該SQL中,表的數據行是以索引的順序來讀取的,這樣就會令讀取它們的開銷變得更大,然而事實上這裏將要獲取的行數卻少得可憐,所以沒有必要在基於行的物理位置進行排序了。
如今咱們須要向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'只是減小了預計輸出的行數,可是並無減小實際開銷,由於咱們仍然須要訪問相同數量的數據行。而該條件並無做爲一個索引條件,而是被當成對索引結果的過濾條件來看待。
若是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)
從查詢規劃中能夠看出(Nested Loop)該查詢語句使用了嵌套循環。外層的掃描是一個位圖索引,所以其開銷與行計數和以前查詢的開銷是相同的,這是由於條件unique1 < 100發揮了做用。 這個時候t1.unique2 = t2.unique2條件子句尚未產生什麼做用,所以它不會影響外層掃描的行計數。然而對於內層掃描而言,當前外層掃描的數據行將被插入到內層索引掃描 中,並生成相似的條件t2.unique2 = constant。因此,內層掃描將獲得和EXPLAIN SELECT * FROM tenk2 WHERE unique2 = 42同樣的計劃和開銷。最後,之外層掃描的開銷爲基礎設置循環節點的開銷,再加上每一個外層行的一個迭代(這裏是 106 * 3.01),以及鏈接處理須要的一點點CPU時間。
若是不想使用嵌套循環的方式來規劃上面的查詢,那麼咱們能夠經過執行如下系統設置,以關閉嵌套循環,如:
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以前不可能得到任何輸出行。
此外,咱們還能夠用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"包括執行器啓動和關閉的時間,以及結果行處理的時間,可是它並不包括分析、重寫或者規劃的時間。
若是EXPLAIN命令僅能用於測試環境,而不能用於真實環境,那它就什麼用都沒有。好比,在一個數據較少的表上執行EXPLAIN,它不能適用於數量 不少的大表,由於規劃器的開銷計算不是線性的,所以它極可能對大些或者小些的表選擇不一樣的規劃。一個極端的例子是一個只佔據一個磁盤頁面的表,在這樣的表 上,無論它有沒有索引可使用,你幾乎都老是獲得順序掃描規劃。規劃器知道無論在任何狀況下它都要進行一個磁盤頁面的讀取,因此再增長几個磁盤頁面讀取用 以查找索引是毫無心義的。
2、批量數據插入:
有如下幾種方法用於優化數據的批量插入。
1. 關閉自動提交:
在批量插入數據時,若是每條數據都被自動提交,當中途出現系統故障時,不只不能保障本次批量插入的數據一致性,並且因爲有屢次提交操做的發生,整個插入 效率也會受到很大的打擊。解決方法是,關閉系統的自動提交,而且在插入開始以前,顯示的執行begin transaction命令,在所有插入操做完成以後再執行commit命令提交全部的插入操做。
2. 使用COPY:
使用COPY在一條命令裏裝載全部記錄,而不是一系列的INSERT命令。COPY命令是爲裝載數量巨大的數據行優化過的,它不像INSERT命令那樣 靈活,可是在裝載大量數據時,系統開銷也要少不少。由於COPY是單條命令,所以在填充表的時就沒有必要關閉自動提交了。
3. 刪除索引:
若是你正在裝載一個新建立的表,最快的方法是建立表,用COPY批量裝載,而後建立表須要的任何索引。由於在已存在數據的表上建立索引比維護逐行增長要快。固然在缺乏索引期間,其它有關該表的查詢操做的性能將會受到必定的影響,惟一性約束也有可能遭到破壞。
4. 刪除外鍵約束:
和索引同樣,"批量地"檢查外鍵約束比一行行檢查更加高效。所以,咱們能夠先刪除外鍵約束,裝載數據,而後在重建約束。
5. 增大maintenance_work_mem:
在裝載大量數據時,臨時增大maintenance_work_mem系統變量的值能夠改進性能。這個系統參數能夠提升CREATE INDEX命令和ALTER TABLE ADD FOREIGN KEY命令的執行效率,可是它不會對COPY操做自己產生多大的影響。
6. 增大checkpoint_segments:
臨時增大checkpoint_segments系統變量的值也能夠提升大量數據裝載的效率。這是由於在向PostgreSQL裝載大量數據時,將會導 致檢查點操做(由系統變量checkpoint_timeout聲明)比平時更加頻繁的發生。在每次檢查點發生時,全部的髒數據都必須flush到磁盤 上。經過提升checkpoint_segments變量的值,能夠有效的減小檢查點的數目。
7. 過後運行ANALYZE:
在增長或者更新了大量數據以後,應該當即運行ANALYZE命令,這樣能夠保證規劃器獲得基於該表的最新數據統計。換句話說,若是沒有統計數據或者統計數據太過陳舊,那麼規劃器極可能會選擇一個較差的查詢規劃,從而致使查詢效率過於低下。 oop