目 錄
創(chuàng)新互聯(lián)建站主要從事成都網站設計、做網站、網頁設計、企業(yè)做網站、公司建網站等業(yè)務。立足成都服務彌勒,10年網站建設經驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:13518219792
總 結
PostgreSQL 通過調用系統(tǒng) fsync() 或者其他使得事務內容寫入到物理磁盤,這樣可以保證操作系統(tǒng)或者數(shù)據庫出現(xiàn)宕機后,仍然可以恢復到某一個一致性的狀態(tài)。理論上講 PostgreSQL 的 fsync 功能關閉,可以實現(xiàn)性能的提升,但是帶來的影響就是需要承擔數(shù)據的丟失,因為出現(xiàn)系統(tǒng)宕機或者數(shù)據庫崩潰的時候有一些數(shù)據是沒有落盤的。
本文將驗證 fsync 參數(shù)的性能影響,以及參數(shù)關閉時數(shù)據庫宕機后的影響。
數(shù)據量:1000W
fsync 參數(shù):on
初始化表:user_info
pgbench 壓測
pgbench 結果
pgbench 壓測
pgbench 結果
數(shù)據量:1000W
fsync 參數(shù):off
初始化表:user_info
pgbench 壓測
pgbench 結果
pgbench 壓測
pgbench 結果
通過對比發(fā)現(xiàn),將 fsync 改為 off,對于讀 TPS,參數(shù) fsync 的影響不大,對于寫 TPS,性能有一定提升。
現(xiàn)在驗證參數(shù)關閉時數(shù)據庫宕機后的影響
首先,使用將數(shù)據庫性能跑起來
然后,模擬服務器斷電
之后,啟動數(shù)據庫
提示信息:比致命錯誤還過分的錯誤。
結果:數(shù)據庫無法啟動,原因就是因為無法找到一個有效的 checkpoint 記錄,這就是因為 fsync 設置為 off,由于數(shù)據庫異常宕機導致??梢酝ㄟ^使用 pg_resetxlog 恢復數(shù)據庫,但是會造成部分數(shù)據無法找回,數(shù)據丟失;也可以通過備份恢復,同樣也會丟失部分數(shù)據。
fsync 參數(shù)對于讀 TPS 的性能影響不大,對于寫 TPS 的性能有一些影響,設置為 off,寫 TPS 性能有一定提升,但是存在數(shù)據庫宕機后無法正常啟動,即使恢復后啟動數(shù)據庫,也會有數(shù)據丟失的很大風險。因此生產環(huán)境非必要時,不要將此參數(shù)設置為 off,還是使用默認的 on 比較穩(wěn)妥。
select * from (select 除了時間所有字段寫出來,to_date('COMMENTTIME','yyyy-mm-dd hh24:mi:ss') time from T_SCHOOL_RECORDCOMMENT where RECORDID = :p_cid) order by time desc;
這樣試試,但是你的時間字段用該保存為date類型,這樣好像很麻煩。。
postgresql(8.2)的配置文件中有一個參數(shù)log_min_duration_statement,意思是只log執(zhí)行時間大于設定值的語句,如果設為0,表示log所有語句;如果設為-1,表示不log任何語句。
看起來,這個配置選項對性能的調整是很有用的,比如可以設置:
log_min_duration_statement = 1000
則只log執(zhí)行時間大于1s的語句,重點優(yōu)化這些sql語句就好了。
然而,奇怪的,這個選項不太容易生效!經過反復試驗,原來需要如下配置:
#debug_print_parse = off
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = off
log_connections = off
#log_disconnections = off
log_duration = off
log_line_prefix = '%t [%p]: [%l-1] ' # Special values:
# %u = user name
# %d = database name
# %r = remote host and port
# %h = remote host
# %p = PID
# %t = timestamp (no milliseconds)
# %m = timestamp with milliseconds
# %i = command tag
# %c = session id
# %l = session line number
# %s = session start timestamp
# %x = transaction id
# %q = stop here in non-session
# processes
# %% = '%'
# e.g. '%u%%%d '
log_statement = 'none' # none, mod, ddl, all
#log_statement = 'all' # none, mod, ddl, all
#log_hostname = off
注意看上面的其中兩個選項的設置:
log_duration = off
log_statement = 'none'
這兩個選項的意思是不log任何sql語句和執(zhí)行時間,但是恰恰是關閉了這兩個,log_min_duration_statement才會生效!可能postgresql內部 對這兩個選項做了“互斥”處理吧。
插入有inserted表就是臨時表刪除deleted表也是臨時表,當執(zhí)行插入或者刪除的時候可以從中查出相關數(shù)據
一、使用EXPLAIN:
PostgreSQL為每個查詢都生成一個查詢規(guī)劃,因為選擇正確的查詢路徑對性能的影響是極為關鍵的。PostgreSQL本身已經包含了一個規(guī)劃器用于尋找最優(yōu)規(guī)劃,我們可以通過使用EXPLAIN命令來查看規(guī)劃器為每個查詢生成的查詢規(guī)劃。
PostgreSQL中生成的查詢規(guī)劃是由1到n個規(guī)劃節(jié)點構成的規(guī)劃樹,其中最底層的節(jié)點為表掃描節(jié)點,用于從數(shù)據表中返回檢索出的數(shù)據行。然而,不同
的掃描節(jié)點類型代表著不同的表訪問模式,如:順序掃描、索引掃描,以及位圖索引掃描等。如果查詢仍然需要連接、聚集、排序,或者是對原始行的其它操作,那
么就會在掃描節(jié)點"之上"有其它額外的節(jié)點。并且這些操作通常都有多種方法,因此在這些位置也有可能出現(xiàn)不同的節(jié)點類型。EXPLAIN將為規(guī)劃樹中的每
個節(jié)點都輸出一行信息,顯示基本的節(jié)點類型和規(guī)劃器為執(zhí)行這個規(guī)劃節(jié)點計算出的預計開銷值。第一行(最上層的節(jié)點)是對該規(guī)劃的總執(zhí)行開銷的預計,這個數(shù)
值就是規(guī)劃器試圖最小化的數(shù)值。
這里有一個簡單的例子,如下:
復制代碼 代碼如下:
EXPLAIN SELECT * FROM tenk1;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
EXPLAIN引用的數(shù)據是:
1). 預計的啟動開銷(在輸出掃描開始之前消耗的時間,比如在一個排序節(jié)點里做排續(xù)的時間)。
2). 預計的總開銷。
3). 預計的該規(guī)劃節(jié)點輸出的行數(shù)。
4). 預計的該規(guī)劃節(jié)點的行平均寬度(單位:字節(jié))。
這里開銷(cost)的計算單位是磁盤頁面的存取數(shù)量,如1.0將表示一次順序的磁盤頁面讀取。其中上層節(jié)點的開銷將包括其所有子節(jié)點的開銷。這里的輸出
行數(shù)(rows)并不是規(guī)劃節(jié)點處理/掃描的行數(shù),通常會更少一些。一般而言,頂層的行預計數(shù)量會更接近于查詢實際返回的行數(shù)。
現(xiàn)在我們執(zhí)行下面基于系統(tǒng)表的查詢:
復制代碼 代碼如下:
SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';
從查詢結果中可以看出tenk1表占有358個磁盤頁面和10000條記錄,然而為了計算cost的值,我們仍然需要知道另外一個系統(tǒng)參數(shù)值。
復制代碼 代碼如下:
postgres=# show cpu_tuple_cost;
cpu_tuple_cost
----------------
0.01
(1 row)
cost = 358(磁盤頁面數(shù)) + 10000(行數(shù)) * 0.01(cpu_tuple_cost系統(tǒng)參數(shù)值)
下面我們再來看一個帶有WHERE條件的查詢規(guī)劃。
復制代碼 代碼如下:
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"應用,這表示該規(guī)劃節(jié)點將掃描表中的每一行數(shù)據,之后再判定它們是否符合過濾的條
件,最后僅輸出通過過濾條件的行數(shù)。這里由于WHERE子句的存在,預計的輸出行數(shù)減少了。即便如此,掃描仍將訪問所有10000行數(shù)據,因此開銷并沒有
真正降低,實際上它還增加了一些因數(shù)據過濾而產生的額外CPU開銷。
上面的數(shù)據只是一個預計數(shù)字,即使是在每次執(zhí)行ANALYZE命令之后也會隨之改變,因為ANALYZE生成的統(tǒng)計數(shù)據是通過從該表中隨機抽取的樣本計算的。
如果我們將上面查詢的條件設置的更為嚴格一些的話,將會得到不同的查詢規(guī)劃,如:
復制代碼 代碼如下:
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)
這里,規(guī)劃器決定使用兩步規(guī)劃,最內層的規(guī)劃節(jié)點訪問一個索引,找出匹配索引條件的行的位置,然后上層規(guī)劃節(jié)點再從表里讀取這些行。單獨地讀取數(shù)據行比順
序地讀取它們的開銷要高很多,但是因為并非訪問該表的所有磁盤頁面,因此該方法的開銷仍然比一次順序掃描的開銷要少。這里使用兩層規(guī)劃的原因是因為上層規(guī)
劃節(jié)點把通過索引檢索出來的行的物理位置先進行排序,這樣可以最小化單獨讀取磁盤頁面的開銷。節(jié)點名稱里面提到的"位圖(bitmap)"是進行排序的機
制。
現(xiàn)在我們還可以將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中,表的數(shù)據行是以索引的順序來讀取的,這樣就會令讀取它們的開銷變得更大,然而事實上這里將要獲取的行數(shù)卻少得可憐,因此沒有必要在基于行的物理位置進行排序了。
現(xiàn)在我們需要向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'只是減少了預計輸出的行數(shù),但是并沒有減少實際開銷,因為我們仍然需要訪問相同數(shù)量的數(shù)據行。而該條件并沒有作為一個索引條件,而是被當成對索引結果的過濾條件來看待。
如果WHERE條件里有多個字段存在索引,那么規(guī)劃器可能會使用索引的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)
這樣的結果將會導致訪問兩個索引,與只使用一個索引,而把另外一個條件只當作過濾器相比,這個方法未必是更優(yōu)。
現(xiàn)在讓我們來看一下基于索引字段進行表連接的查詢規(guī)劃,如:
復制代碼 代碼如下:
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)
從查詢規(guī)劃中可以看出(Nested
Loop)該查詢語句使用了嵌套循環(huán)。外層的掃描是一個位圖索引,因此其開銷與行計數(shù)和之前查詢的開銷是相同的,這是因為條件unique1
100發(fā)揮了作用。 這個時候t1.unique2 =
t2.unique2條件子句還沒有產生什么作用,因此它不會影響外層掃描的行計數(shù)。然而對于內層掃描而言,當前外層掃描的數(shù)據行將被插入到內層索引掃描
中,并生成類似的條件t2.unique2 = constant。所以,內層掃描將得到和EXPLAIN SELECT * FROM tenk2
WHERE unique2 = 42一樣的計劃和開銷。最后,以外層掃描的開銷為基礎設置循環(huán)節(jié)點的開銷,再加上每個外層行的一個迭代(這里是 106
* 3.01),以及連接處理需要的一點點CPU時間。
如果不想使用嵌套循環(huán)的方式來規(guī)劃上面的查詢,那么我們可以通過執(zhí)行以下系統(tǒng)設置,以關閉嵌套循環(huán),如:
復制代碼 代碼如下:
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)
這個規(guī)劃仍然試圖用同樣的索引掃描從tenk1里面取出符合要求的100行,并把它們存儲在內存中的散列(哈希)表里,然后對tenk2做一次全表順序掃
描,并為每一條tenk2中的記錄查詢散列(哈希)表,尋找可能匹配t1.unique2 =
t2.unique2的行。讀取tenk1和建立散列表是此散列聯(lián)接的全部啟動開銷,因為我們在開始讀取tenk2之前不可能獲得任何輸出行。
此外,我們還可以用EXPLAIN ANALYZE命令檢查規(guī)劃器預估值的準確性。這個命令將先執(zhí)行該查詢,然后顯示每個規(guī)劃節(jié)點內實際運行時間,以及單純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"數(shù)值是以真實時間的毫秒來計算的,而"cost"預估值是以磁盤頁面讀取數(shù)量來計算的,所以它們很可能是不一致的。然而我們需要關注的只是兩組數(shù)據的比值是否一致。
在一些查詢規(guī)劃里,一個子規(guī)劃節(jié)點很可能會運行多次,如之前的嵌套循環(huán)規(guī)劃,內層的索引掃描會為每個外層行執(zhí)行一次。在這種情況下,"loops"將報告
該節(jié)點執(zhí)行的總次數(shù),而顯示的實際時間和行數(shù)目則是每次執(zhí)行的平均值。這么做的原因是令這些真實數(shù)值與開銷預計顯示的數(shù)值更具可比性。如果想獲得該節(jié)點所
花費的時間總數(shù),計算方式是用該值乘以"loops"值。
EXPLAIN ANALYZE顯示的"Total runtime"包括執(zhí)行器啟動和關閉的時間,以及結果行處理的時間,但是它并不包括分析、重寫或者規(guī)劃的時間。
如果EXPLAIN命令僅能用于測試環(huán)境,而不能用于真實環(huán)境,那它就什么用都沒有。比如,在一個數(shù)據較少的表上執(zhí)行EXPLAIN,它不能適用于數(shù)量很
多的大表,因為規(guī)劃器的開銷計算不是線性的,因此它很可能對大些或者小些的表選擇不同的規(guī)劃。一個極端的例子是一個只占據一個磁盤頁面的表,在這樣的表
上,不管它有沒有索引可以使用,你幾乎都總是得到順序掃描規(guī)劃。規(guī)劃器知道不管在任何情況下它都要進行一個磁盤頁面的讀取,所以再增加幾個磁盤頁面讀取用
以查找索引是毫無意義的。
二、批量數(shù)據插入:
有以下幾種方法用于優(yōu)化數(shù)據的批量插入。
1. 關閉自動提交:
在批量插入數(shù)據時,如果每條數(shù)據都被自動提交,當中途出現(xiàn)系統(tǒng)故障時,不僅不能保障本次批量插入的數(shù)據一致性,而且由于有多次提交操作的發(fā)生,整個插入效
率也會受到很大的打擊。解決方法是,關閉系統(tǒng)的自動提交,并且在插入開始之前,顯示的執(zhí)行begin
transaction命令,在全部插入操作完成之后再執(zhí)行commit命令提交所有的插入操作。
2. 使用COPY:
使用COPY在一條命令里裝載所有記錄,而不是一系列的INSERT命令。COPY命令是為裝載數(shù)量巨大的數(shù)據行優(yōu)化過的,它不像INSERT命令那樣靈
活,但是在裝載大量數(shù)據時,系統(tǒng)開銷也要少很多。因為COPY是單條命令,因此在填充表的時就沒有必要關閉自動提交了。
3. 刪除索引:
如果你正在裝載一個新創(chuàng)建的表,最快的方法是創(chuàng)建表,用COPY批量裝載,然后創(chuàng)建表需要的任何索引。因為在已存在數(shù)據的表上創(chuàng)建索引比維護逐行增加要快。當然在缺少索引期間,其它有關該表的查詢操作的性能將會受到一定的影響,唯一性約束也有可能遭到破壞。
4. 刪除外鍵約束:
和索引一樣,"批量地"檢查外鍵約束比一行行檢查更加高效。因此,我們可以先刪除外鍵約束,裝載數(shù)據,然后在重建約束。
5. 增大maintenance_work_mem:
在裝載大量數(shù)據時,臨時增大maintenance_work_mem系統(tǒng)變量的值可以改進性能。這個系統(tǒng)參數(shù)可以提高CREATE
INDEX命令和ALTER TABLE ADD FOREIGN KEY命令的執(zhí)行效率,但是它不會對COPY操作本身產生多大的影響。
6. 增大checkpoint_segments:
臨時增大checkpoint_segments系統(tǒng)變量的值也可以提高大量數(shù)據裝載的效率。這是因為在向PostgreSQL裝載大量數(shù)據時,將會導致
檢查點操作(由系統(tǒng)變量checkpoint_timeout聲明)比平時更加頻繁的發(fā)生。在每次檢查點發(fā)生時,所有的臟數(shù)據都必須flush到磁盤上。
通過提高checkpoint_segments變量的值,可以有效的減少檢查點的數(shù)目。
7. 事后運行ANALYZE:
在增加或者更新了大量數(shù)據之后,應該立即運行ANALYZE命令,這樣可以保證規(guī)劃器得到基于該表的最新數(shù)據統(tǒng)計。換句話說,如果沒有統(tǒng)計數(shù)據或者統(tǒng)計數(shù)據太過陳舊,那么規(guī)劃器很可能會選擇一個較差的查詢規(guī)劃,從而導致查詢效率過于低下。