PostgreSQL9.6的新特性並行查詢



PostgreSQL20169月發布了9.6版本,在該版本中新增了並行計算功能,目前PG支持的並行查詢主要是順序掃描(Sequencial Scans),並且支持部分鏈接查詢(join)和聚合(aggregation)。

並行查詢涉及的參數

max_worker_processes:決定了整個數據庫集群允許啟動多少個work process(注意如果有standbystandby的參數必須大於等於主庫的參數值)。設置為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_costwoker進程處理完后的tuple要傳輸給上層node,即進程間查詢結果的交換成本,即后台進程間傳輸一個元組的代價。其值越小,數據庫越有可能使用並行。

force_parallel_mode: 主要用於測試,on/true表示強制使用並行查詢。

parallel_workers:設置表級並行度,可在建表時設置,也可后期設置

PostgreSQL優化器計算並行度及如何決定使用並行

1、確定整個系統能開多少worker進程(max_worker_processes

2、計算並行計算的成本,優化器根據CBO原則選擇是否開啟並行(parallel_setup_costparallel_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 FROM generate_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.823 rows=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.678 rows=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.57 rows=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.285 rows=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=45139 width=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.12 rows=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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM