pg9.6 並行查詢
PostgreSQL在2016年9月釋出了9.6版本,在該版本中新增了平行計算功能,目前PG支援的並行查詢主要是順序掃描(Sequencial Scans),並且支援部分連結查詢(join)和聚合(aggregation)。
並行查詢涉及的引數
max_worker_processes:決定了整個資料庫叢集允許啟動多少個work process(注意如果有standby,standby的引數必須大於等於主庫的引數值)。設定為0,表示不允許並行。
max_parallel_workers_per_gather: 最多會有多少個後臺程序來一起完成當前查詢,推薦值為1-4。這些workers主要來自max_worker_processes(程序池的大小)。在OLTP業務中,因為每個worker都會消耗同等的work_mem等資源,可能會產生比較嚴重的爭搶。
min_parallel_relation_size: 啟用並行查詢的最小資料表的大小,作為是否啟用平行計算的條件之一,如果小於它,不啟用平行計算。並不是所有小於它的表一定不會啟用並行。
parallel_setup_cost:表示啟動woker process的啟動成本,因為啟動worker程序需要建立共享記憶體等操作,屬於附帶的額外成本。其值越小,資料庫越有可能使用並行查詢。
parallel_tuple_cost:woker程序處理完後的tuple要傳輸給上層node,即程序間查詢結果的交換成本,即後臺程序間傳輸一個元組的代價。其值越小,資料庫越有可能使用並行。
force_parallel_mode: 主要用於測試,on/true表示強制使用並行查詢。
parallel_workers:設定表級並行度,可在建表時設定,也可後期設定
PostgreSQL優化器計算並行度及如何決定使用並行
1、確定整個系統能開多少worker程序(max_worker_processes)
2、計算平行計算的成本,優化器根據CBO原則選擇是否開啟並行(parallel_setup_cost、parallel_tuple_cost)。
3、強制開啟並行(force_parallel_mode)。
4、根據表級parallel_workers引數決定每個查詢的並行度取最小值(parallel_workers, max_parallel_workers_per_gather)
5、當表沒有設定parallel_workers引數,並且表的大小大於min_parallel_relation_size時,由演算法決定每個查詢的並行度。
並行順序掃描測試
什麼是順序操作
順序操作(同oracle中的全表掃描),意味著資料庫會按順序讀取整張表,逐行確認是否符合查詢條件。一般來說,當你關注給定查詢語句的執行時間時,需要關注順序操作。由以上可知,對於一個單表查詢來說,順序操作的時間複雜度為O(n)。對於時間敏感的查詢,走索引是更好的選擇,索引(預設的二叉樹索引)有更好的時間複雜度O(log(n))。但使用索引是有代價的:在進行插入和更新操作時,需要花費額外的時間更新索引,並佔用額外的記憶體和磁碟空間。因此,在一些情況下不使用索引,走順序操作可能是更好的選擇。以上這些需要根據實際情況取捨。
首先建立一個people表,只有id(主鍵)和age列:
postgres=# CREATETABLE people (id int PRIMARY KEY NOT NULL, age int NOT NULL);
CREATE TABLE
postgres=# \d people
Table "public.people"
Column |Type | Modifiers
-------+---------+-----------
id | integer | not null
age | integer | not null
Indexes:
"people_pkey" PRIMARY KEY, btree (id)
插入一些資料。一千萬行應該足以看到平行計算的用處。表中每個人的年齡取0~100的隨機數。
postgres=# INSERTINTO people SELECT id, (random()*100)::integer AS age FROMgenerate_series(1,10000000) AS id;
INSERT 0 10000000
現在嘗試獲取所有年齡為6歲的人,預計獲取約百分之一的行。
postgres=# EXPLAINANALYZE SELECT * FROM people WHERE age =6;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Seq Scan on people (cost=0.00..169247.71 rows=104000 width=8) (actual time=0.052..1572.701 rows=100310 loops=1)
Filter: (age = 6)
Rows Removed by Filter: 9899690
Planning time: 0.061 ms
Execution time: 1579.476 ms
(5 rows)
上面查詢花了1579.476 ms。並行查詢預設是禁用的。現在啟用並行查詢,允許PostgreSQL最多使用兩個並行,然後再次執行該查詢。
postgres=# SET max_parallel_workers_per_gather = 2;
SET
postgres=# EXPLAINANALYZE SELECT * FROM people WHERE age =6;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Gather(cost=1000.00..107731.21 rows=104000 width=8) (actual time=0.431..892.823rows=100310 loops=1)
Workers Planned: 2
Workers Launched: 2
->Parallel Seq Scan on people(cost=0.00..96331.21 rows=43333 width=8) (actual time=0.109..862.562 rows=33437 loops=3)
Filter: (age = 6)
Rows Removed by Filter: 3299897
Planning time: 0.133 ms
Execution time: 906.548 ms
(8 rows)
使用並行查詢後,同樣語句查詢事件縮減到906.548 ms,還不到原來時間的一半。啟用並行查詢收集資料並將“收集”的資料進行聚合會帶來額外的開銷。每增加一個並行,開銷也隨之增大。有時更多的並行並不能改善查詢效能。但為了驗證並行的效能,你需要在資料庫伺服器上進行試驗,因為伺服器擁有更多的CPU核心。
不是所有的查詢都會使用並行。例如嘗試獲取年齡低於50的資料(這將返回一半資料)
postgres=# EXPLAINANALYZE SELECT * FROM people WHERE age <50;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Seq Scan on people (cost=0.00..169247.71 rows=4955739 width=8) (actual time=0.079..1957.076 rows=4949330 loops=1)
Filter: (age < 50)
Rows Removed by Filter: 5050670
Planning time: 0.097 ms
Execution time: 2233.848 ms
(5 rows)
上面的查詢返回表中的絕大多數資料,沒有使用並行,為什麼會這樣呢? 當查詢只返回表的一小部分時,平行計算程序啟動、執行(匹配查詢條件)及合併結果集的開銷小於序列計算的開銷。當返回表中大部分資料時,平行計算的開銷可能會高於其所帶來的好處。
如果要強制使用並行,可以強制設定平行計算的開銷為0,如下所示:
postgres=# SET parallel_tuple_cost TO 0;
SET
postgres=# EXPLAINANALYZE SELECT * FROM people WHERE age <50;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
Gather(cost=1000.00..97331.21 rows=4955739 width=8) (actual time=0.424..3147.678rows=4949330 loops=1)
Workers Planned: 2
Workers Launched: 2
->Parallel Seq Scan on people(cost=0.00..96331.21 rows=2064891 width=8) (actual time=0.082..1325.310 rows=1649777 loops=3)
Filter: (age < 50)
Rows Removed by Filter: 1683557
Planning time: 0.104 ms
Execution time: 3454.690 ms
(8 rows)
從上面結果中可以看到,強制並行後,查詢語句執行時間由2233.848 ms增加到3454.690 ms,說明平行計算的開銷是真實存在的。
聚合函式的平行計算測試
測試之前,現重置一下現有環境
postgres=# SET parallel_tuple_cost TO DEFAULT;
SET
postgres=# SET max_parallel_workers_per_gather TO 0;
SET
下面語句在未開啟並行時,計算所有人的平均年齡
postgres=# EXPLAINANALYZE SELECT avg(age) FROM people;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=169247.72..169247.73 rows=1 width=32) (actual time=2751.862..2751.862 rows=1 loops=1)
->Seq Scan on people (cost=0.00..144247.77 rows=9999977 width=4) (actual time=0.054..1250.670 rows=10000000 loops=1)
Planning time: 0.054 ms
Execution time: 2751.905 ms
(4 rows)
開啟並行後,再次計算平均年齡
postgres=# SET max_parallel_workers_per_gather TO 2;
SET
postgres=# EXPLAINANALYZE SELECT avg(age) FROM people;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=97331.43..97331.44 rows=1 width=32) (actual time=1616.346..1616.346 rows=1 loops=1)
->Gather (cost=97331.21..97331.42 rows=2 width=32) (actual time=1616.143..1616.316 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=96331.21..96331.22 rows=1 width=32) (actual time=1610.785..1610.785 rows=1 loops=3)
-> Parallel Seq Scan on people (cost=0.00..85914.57rows=4166657 width=4) (actual time=0.067..957.355 rows=3333333 loops=3)
Planning time: 0.248 ms
Execution time: 1619.181 ms
(8 rows)
從上面兩次查詢中可以看到,平行計算將查詢時間由2751.905 ms降低到了1619.181ms。
join並行測試
建立測試環境。建立一個1000萬行的pets表。
postgres=# CREATETABLE pets (owner_id int NOT NULL, species character(3) NOTNULL);
postgres=# CREATEINDEX pets_owner_id ON pets (owner_id);
postgres=# INSERTINTO pets SELECT (random()*10000000)::integer AS owner_id, ('{cat,dog}'::text[])[ceil(random()*2)] as species FROM generate_series(1,10000000);
不啟用平行計算,執行join語句
postgres=# SET max_parallel_workers_per_gather TO 0;
SET
postgres=# EXPLAINANALYZE SELECT * FROM pets JOIN people ON pets.owner_id =people.id WHERE pets.species = 'cat' AND people.age = 18;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=171025.88..310311.99 rows=407 width=28) (actual time=1627.973..5963.378 rows=49943 loops=1)
Hash Cond: (pets.owner_id = people.id)
->Seq Scan on pets (cost=0.00..138275.00 rows=37611 width=20) (actual time=0.050..2784.238 rows=4997112 loops=1)
Filter: (species = 'cat'::bpchar)
Rows Removed by Filter: 5002888
->Hash (cost=169247.71..169247.71 rows=108333 width=8) (actual time=1626.987..1626.987 rows=100094 loops=1)
Buckets: 131072 Batches: 2 Memory Usage: 2974kB
-> Seq Scan on people (cost=0.00..169247.71 rows=108333 width=8) (actual time=0.045..1596.765 rows=100094 loops=1)
Filter: (age = 18)
Rows Removed by Filter: 9899906
Planning time: 0.466 ms
Execution time: 5967.223 ms
(12 rows)
以上查詢花費這幾乎是5967.223 ms,下面啟用平行計算
postgres=# SET max_parallel_workers_per_gather TO 2;
SET
postgres=# EXPLAINANALYZE SELECT * FROM pets JOIN people ON pets.owner_id =people.id WHERE pets.species = 'cat' AND people.age = 18;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Gather(cost=1000.43..244061.39 rows=53871 width=16) (actual time=0.304..1295.285rows=49943 loops=1)
Workers Planned: 2
Workers Launched: 2
->Nested Loop (cost=0.43..237674.29 rows=22446 width=16) (actual time=0.347..1274.578 rows=16648 loops=3)
-> Parallel Seq Scan on people (cost=0.00..96331.21 rows=45139width=8) (actual time=0.147..882.415 rows=33365 loops=3)
Filter: (age = 18)
Rows Removed by Filter: 3299969
-> Index Scan using pets_owner_id on pets (cost=0.43..3.12rows=1 width=8) (actual time=0.010..0.011 rows=0 loops=100094)
Index Cond: (owner_id = people.id)
Filter: (species = 'cat'::bpchar)
Rows Removed by Filter: 1
Planning time: 0.274 ms
Execution time: 1306.590 ms
(13 rows)
由以上可知,查詢語句的執行時間從5967.223 ms降低到1306.590 ms。
————————————————
原文連結:https://blog.csdn.net/pg_hgdb/article/details/79156535