陳煥生:深入理解Oracle 的並行執行
Oracle並行執行是一種分而治之的方法。執行一個sql 時,分配多個並行程序同時執行資料掃描,連線以及聚合等操作,使用更多的資源,得到更快的sql 響應時間。並行執行是充分利用硬體資源,處理大量資料時的核心技術。
在本文中,在一個簡單的星型模型上,我會使用大量例子和sql monitor 報告,力求以最直觀簡單的方式,向讀者闡述並行執行的核心內容:
• Oracle 並行執行為什麼使用生產者——消費者模型。
• 如何閱讀並行執行計劃。
• 不同的資料分發方式分別適合什麼樣的場景。
• 使用partition wise join 和並行執行的組合提高效能。
• 資料傾斜會對不同的分發方式帶來什麼影響。
• 由於生產者-‐消費者模型的限制,執行計劃中可能出現阻塞點。
• 布隆過濾是如何提高並行執行效能的。
• 現實世界中,使用並行執行時最常見的問題。
術語說明:
- S: 時間單位秒。
- K: 數量單位一千。
- M: 數量單位一百萬, 或者時間單位分鐘。
- DoP: Degree of Parallelism, 並行執行的並行度。
- QC: 並行查詢的 Query Coordinator。
- PX 程序: Parallel Execution Slaves。
- AAS: Average active session, 並行執行時平均的活動會話數。
- 分發: pq distribution method, 並行執行的分發方式, 包括 replicate, broadcast, hash 和 adaptive分發等 4 種方式, 其中 adaptive 分發是 12c 引入的的新特性, 我將在本篇文章中一一闡述。
- Hash join 的左邊: 驅動表, the build side of hash join, 一般為小表。
- Hash join 的右邊: 被驅動表, the probe side of hash join, 一般為大表。
- 布隆過濾: bloom filter, 一種記憶體資料結構, 用於判斷一個元素是否屬於一個集合。
測試環境和資料
Oracle版本為12.1.0.2.2,兩個節點的RAC,硬體為ExadataX3-‐8。
這是一個典型的星型模型,事實表lineorder有3億行記錄,維度表part/customer分別包含1.2M
和1.5M行記錄,3個表都沒有進行分割槽,lineorder大小接近30GB。
- select
- owner seg_owner,
- segment_name seg_segment_name, round(bytes/1048576,2) SEG_MB
- from
- dba_segments where
- owner = 'SID'
- and segment_name in ('LINEORDER','PART','CUSTOMER')
- /
- OWNER SEGMENT_NAME SEGMENT_TYPE SEG_MB
- ------ ------------ ------------ -------- SID LINEORDER TABLE 30407.75
- SID CUSTOMER TABLE 168
- SID PART TABLE 120
select owner seg_owner, segment_name seg_segment_name, round(bytes/1048576,2) SEG_MB from dba_segments where owner = 'SID' and segment_name in ('LINEORDER','PART','CUSTOMER') / OWNER SEGMENT_NAME SEGMENT_TYPE SEG_MB ------ ------------ ------------ -------- SID LINEORDER TABLE 30407.75 SID CUSTOMER TABLE 168 SID PART TABLE 120
本篇文章所有的測試,除非特別的說明,我關閉了12c的adaptive plan特性,引數optimizer_adaptive_features被預設設定為false。Adaptive相關的特性如cardinality feedback,adaptive distribution method,adaptive join都不會啟用。如果檢查執行計劃的outline資料,你會發現7個優化器相關的隱含引數被設定為關閉狀態。事實上,12c優化器因為引入adaptive plan特性,比以往版本複雜得多,剖析12c的優化器的各種新特性,我覺得非常具有挑戰性,或許我會在另一篇文章裡嘗試一下。
- select * from table(dbms_xplan.display_cursor('77457qc9a324k',0,’outline’));
- ...
- Outline Data
- -------------
- /*+
- BEGIN_OUTLINE_DATA
- IGNORE_OPTIM_EMBEDDED_HINTS
- OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
- DB_VERSION('12.1.0.2')
- OPT_PARAM('_optimizer_use_feedback' 'false')
- OPT_PARAM('_px_adaptive_dist_method' 'off')
- OPT_PARAM('_optimizer_dsdir_usage_control' 0)
- OPT_PARAM('_optimizer_adaptive_plans' 'false')
- OPT_PARAM('_optimizer_strans_adaptive_pruning' 'false')
- OPT_PARAM('_optimizer_gather_feedback' 'false')
- OPT_PARAM('_optimizer_nlj_hj_adaptive_join' 'false')
- OPT_PARAM('optimizer_dynamic_sampling' 11)
- ALL_ROWS
- ……
- END_OUTLINE_DATA
- */
select * from table(dbms_xplan.display_cursor('77457qc9a324k',0,’outline’)); ... Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('12.1.0.2') DB_VERSION('12.1.0.2') OPT_PARAM('_optimizer_use_feedback' 'false') OPT_PARAM('_px_adaptive_dist_method' 'off') OPT_PARAM('_optimizer_dsdir_usage_control' 0) OPT_PARAM('_optimizer_adaptive_plans' 'false') OPT_PARAM('_optimizer_strans_adaptive_pruning' 'false') OPT_PARAM('_optimizer_gather_feedback' 'false') OPT_PARAM('_optimizer_nlj_hj_adaptive_join' 'false') OPT_PARAM('optimizer_dynamic_sampling' 11) ALL_ROWS …… END_OUTLINE_DATA */
並行初體驗
序列執行
以下sql對customers和lineorder連線之後,計算所有訂單的全部利潤。 序列執行時不使用parallel hint:
[js] view plaincopyprint?- select /*+ monitor */
- sum(lo_revenue)
- from
- lineorder, customer
- where
- lo_custkey = c_custkey;
select /*+ monitor */ sum(lo_revenue) from lineorder, customer where lo_custkey = c_custkey;
序列執行時,sql執行時間為1.5分鐘,dbtime為1.5分鐘。執行計劃有5行,一個使用者程序工作完成了對customer,lineorder兩個表的掃描,hashjoin,聚合以及返回資料的所有操作。此時AAS(average active sessions)為1,sql執行時間等於db time。幾乎所有的dbtime都為db cpu,72%的cpu花在了第二行的hash join操作。因為測試機器為一臺Exadata X3——8,30GB的IO請求在一秒之內處理完成。Celloffload
Efficiency等於87%意味著經過儲存節點掃描,過濾不需要的列,最終返回計算節點的資料大小隻有30GB的13%。
並行執行
使用hint parallel(4),指定DoP=4並行執行同樣的sql:
- select /*+ monitor parallel(4)*/
- sum(lo_revenue)
- from
- lineorder, customer
- where
- lo_custkey = c_custkey;
select /*+ monitor parallel(4)*/ sum(lo_revenue) from lineorder, customer where lo_custkey = c_custkey;
SQL執行時間為21s,db time為1.4分鐘。DoP=4,在兩個例項上執行。執行計劃從5行增加為9行,從下往上分別多了’PXBLOCKITERATOR’, ‘SORTAGGREGATE’, ‘PXSENDQC(RANDOM)’ 和 ’PXCOORDINATOR’ 這四個操作。
其中3到8行的操作為並行處理,sql的執行順序為:每個PX程序掃描維度表customer(第6行),以資料塊地址區間作為單位(第7行)掃描四分之一的事實表lineorder(第8行),接著進行hash join(第5行),然後對連線之後的資料做預先聚合(第4行),最後把結果給QC(第三行)。QC接收資料(第2行)之後,做進一步的彙總(第1行),最後返回資料(第0行)。
SQL執行時間比原來快了4倍,因為最消耗時間的操作,比如對lineorder的全表掃描,hashjoin和聚合,我們使用4個程序並行處理,因此最終sql執行時間為序列執行的1/4。另一方面,dbtime並沒有明顯下降,並行時1.4m,序列時為1.5m,從系統的角度看,兩次執行消耗的系統資源是一樣的。
DoP=4時,因為沒有涉及資料的分發(distribution),QC只需分配一組PX程序,四個PX程序分別為例項1和2的p000/p0001。我們可以從系統上檢視這4個PX程序。每個PX程序消耗大致一樣的db time,CPU和IO資源。AAS=4,這是最理想的情況,每個PX程序完成同樣的工作量,一直保持活躍。沒有序列點,沒有並行執行傾斜。
AAS=4,檢視活動資訊時,為了更好的展示活動資訊,注意點掉”CPU Cores”這個複選框。
在Linux系統上顯示這四個PX程序。
- [[email protected] sidney]$ ps -ef | egrep "p00[01]_SSB"
- oracle 20888 1 4 2014 ? 18:50:59 ora_p000_SSB1
- oracle 20892 1 4 2014 ? 19:01:29 ora_p001_SSB1
- [[email protected] sidney]$ ssh exa01db02 'ps -ef | egrep "p00[01]_SSB"'
- oracle 56910 1 4 2014 ? 19:01:03 ora_p000_SSB2
- oracle 56912 1 4 2014 ? 18:53:30 ora_p001_SSB2
[[email protected] sidney]$ ps -ef | egrep "p00[01]_SSB" oracle 20888 1 4 2014 ? 18:50:59 ora_p000_SSB1 oracle 20892 1 4 2014 ? 19:01:29 ora_p001_SSB1 [[email protected] sidney]$ ssh exa01db02 'ps -ef | egrep "p00[01]_SSB"' oracle 56910 1 4 2014 ? 19:01:03 ora_p000_SSB2 oracle 56912 1 4 2014 ? 18:53:30 ora_p001_SSB2
小結
本節的例子中,DoP=4,並行執行時分配了4個PX程序,帶來4倍的效能提升。SQL monitor報告包含了並行執行的總體資訊和各種細節,比如QC,DoP,並行執行所在的例項,每個PX程序消耗的資源,以及執行SQL時AAS。
生產者-消費者模型
在上面並行執行的例子中,每個px程序都會掃描一遍維度表customer,然後掃描事實表lineorder進行hash join。這時沒有資料需要進行分發,只需要分配一組px程序。這種replicate維度表的行為,是12c的新特性,由引數_px_replication_enabled控制。
更常見情況是並行執行時,QC需要分配兩組PX程序,互為生產者和消費者協同工作,完成並行執行計劃。架構圖1如下:
Broadcast分發,一次資料分發
為了舉例說明兩組px程序如何協作的,設定_px_replication_enabled為false。QC會分配兩組PX程序,一組為生產者,一組為消費者。
見下圖,此時sql執行時間為23s,執行時間變慢了2s,dbtime仍為1.5分鐘。
最大的變化來自執行計劃,現在執行計劃有12行。增加了對customer的並行掃描 PXBLOCKITERATOR (第8行),分發’PXSENDBROADCAST’和接收’PXRECEIVE’。執行計劃中出現了兩組PX程序,除了之前藍色的多人標誌,現在出現了紅色的多人標誌。此時,SQL的執行順序為:
- 4個紅色的PX程序扮演生產者角色,掃描維度表customer,把資料通過broadcast的方式分發給每一個扮演消費者的藍色PX程序。因為DoP=4,每一條被掃描出來的記錄被複制了4份,從sqlmonitor的第9行,customer全表掃描返回1。5m行資料,第8行的分發和第7行的接受之時,變成了6m行記錄,每個作為消費者的藍色px程序都持有了一份完整包含所有custome記錄的資料,並準備好第5行hashjoin的buildtable。
- 4個作為消費者的藍色PX程序,以資料塊地址區間為單位掃描事實表lineorder(第10/11行);同時和已經持有的customer表的資料進hashjoin(第5行),然後對滿足join條件的資料做預聚合(第4行),因為我們查詢的目標是對所有lo_revenue求和,聚合之後每個PX程序只需輸出一個總數。
- 4個藍色的PX程序反過來作為生產者,把聚合的資料發給消費者QC(第3行和第2行)。由QC對接收到4行記錄做最後的聚合,然後返回給使用者。
- 使用broadcast的分發方式,只需要把customer的資料廣播給每個消費者。Lineorder的數不需要重新分發。因為lineorder的資料量比customer大的多,應該避免對lineorder的資料進行分發,這種執行計劃非常適合星型模型的資料。
觀察sql monitor報告中Parallel標籤下的資訊,紅色的PX程序為例項1、2上的p002/p003程序,藍色的PX程序為p000/p001程序,因為藍色的PX程序負責掃描事實表lineorder,hash join和聚合,所以消耗幾乎所有的db time。
生產者-消費者模型工作原理
並行查詢之後,可以通過檢視V$PQ_TQSTAT,驗證以上描述的執行過程。
- 例項1、2上的p002/p003程序作為生產者,幾乎平均掃描customer的1/4記錄,把每一條記錄廣播給4個消費者PX程序,傳送的記錄數之和為6m行。通過table queue0(TQ_ID=0),每個作為消費者的p000/p001程序,接收了完整的1。5m行customer記錄,接收的記錄數之和為6m行。
- 例項1、2上的p000/p0001程序作為生產者,通過table queue1(TQ_ID=1),把聚合的一條結果記錄發給作為消費者的QC。QC作為消費者,接收了4行記錄。
- SELECT
- dfo_number, tq_id, server_type, instance, process, num_rows
- FROM
- V$PQ_TQSTAT
- ORDER BY
- dfo_number DESC, tq_id, server_type desc, instance, process;
- DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS
- ---------- ---------- -------------- ---------- --------- ----------
- 1 0 Producer 1 P002 1461932
- 1 0 Producer 1 P003 1501892
- 1 0 Producer 2 P002 1575712
- 1 0 Producer 2 P003 1460464
- 1 0 Consumer 1 P000 1500000
- 1 0 Consumer 1 P001 1500000
- 1 0 Consumer 2 P000 1500000
- 1 0 Consumer 2 P001 1500000
- 1 1 Producer 1 P000 1
- 1 1 Producer 1 P001 1
- 1 1 Producer 2 P000 1
- 1 1 Producer 2 P001 1
- 1 1 Consumer 1 QC 4
- 13 rows selected.
SELECT dfo_number, tq_id, server_type, instance, process, num_rows FROM V$PQ_TQSTAT ORDER BY dfo_number DESC, tq_id, server_type desc, instance, process; DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS ---------- ---------- -------------- ---------- --------- ---------- 1 0 Producer 1 P002 1461932 1 0 Producer 1 P003 1501892 1 0 Producer 2 P002 1575712 1 0 Producer 2 P003 1460464 1 0 Consumer 1 P000 1500000 1 0 Consumer 1 P001 1500000 1 0 Consumer 2 P000 1500000 1 0 Consumer 2 P001 1500000 1 1 Producer 1 P000 1 1 1 Producer 1 P001 1 1 1 Producer 2 P000 1 1 1 Producer 2 P001 1 1 1 Consumer 1 QC 4 13 rows selected.
那麼,以上的輸出中,DFO_NUMBER和TQ_ID這兩列表示什麼意思呢?
- DFO代表Data Flow Operator,是執行計劃中可以並行執行的操作。一個QC代表一棵DFO樹(tree),包含多個DFO;同一個QC中所有並行操作的DFO_NUMBER是相同的,此例中,所有DFO_NUMBER為1。執行計劃包含多個QC的例子也不少見,比如使用unionall的語句,unionall每個分支都是獨立的DFO樹,不同的DFO樹之間可以並行執行。本篇文章僅討論執行計劃只有一個QC的情況。
- TQ代表table queue,用以PX程序之間或者和QC通訊連線。以上執行計劃中,table queue0為PX程序之間的連線,table queue1為PX程序和QC之間的連線。生產者通過table queue分發資料,消費者從tablequeue接收資料。不同的table queue編號,代表了不同的資料分發。通過table queue,我們可以理解Oracle並行執行使用生產者-‐消費者模型的本質:
- 同一棵DFO樹中,最多隻有兩組PX程序。每個生產者程序都存在一個和每個消費者程序的連線,每個PX程序和QC都存在一個連線。假設DoP=n,連線總數為(n*n+2*n),隨著n的增長,連線總數會爆炸型增長。Oracle並行執行設計時,採用生產者和消費者模型,考慮到連線數的複雜度,每個DFO最多隻分配兩組PX程序。假設DoP=100時,兩組PX程序之間的連線總數為10000。假設可以分配三組PX程序一起完成並行執行計劃,那麼三組PX之間連線總數會等於1百萬,維護這麼多連線,是一個不可能的任務。
- 同一棵DFO樹中,兩組PX程序之間,同一時間只存在一個活躍的資料分發。如果執行路徑很長,資料需要多次分發,兩組PX程序會變換生產者消費者角色,相互協作,完成所有並行操作。每次資料分發,對應的tablequeue的編號不同。一個活躍的資料分發過程,需要兩組PX程序都參與,一組為生產者傳送資料,一組為消費者接收資料。因為一個DFO裡最多隻有兩組PX程序,意味著,PX程序之間,同一時間只能有一個活躍的資料分發。如果PX程序在執行計劃中需要多次分發資料,可能需要在執行計劃插入一些阻塞點,比如BUFFERSORT和HASHJOINBUFFERED這兩個操作,保證上一次的資料分發完成之後,才開始下一次分發。在後面的章節,我將會說明這些阻塞點帶來什麼影響。這個例子中,tablequeue0和1可以同時工作是因為:tablequeue0是兩組PX程序之間的連結,tablequeue1為PX程序和QC之間的連線,tablequeue0與tablequeue1是相互獨立的,因此可以同時進行。
- PX程序之間或者與QC的連線至少存在一個(單節點下至多三個,RAC環境下至多四個)訊息緩衝區用於程序間資料互動,該訊息緩衝區預設在Largepool中分配(如果沒有配置Largepool則在Sharedpool中分配)。多個緩衝區是為了實現非同步通訊,提高效能。
- 每個訊息緩衝區的大小由引數parallel_execution_message_size控制,預設為16k。
- 當兩個程序都在同一個節點的時候,通過在Largepool(如果沒有配置Largepool則Sharedpool)中傳遞和接收訊息緩衝進行資料互動。當兩個程序位於不同節點時。通過RAC心跳網路進行資料互動,其中一方接收的資料需要快取在本地Largepool(如果沒有配置Largepool則Sharedpool)裡面。
小結
為了說明並行執行的生產者--消費者模型是如何工作的,我使用了broad cast分發,QC分配兩組PX程序,一組為生產者,一組為消費者。QC和PX程序之間,兩組PX程序之間通過table queue進行資料分發,協同完成整個並行執行計劃。檢視V$PQ_TQSTAT記錄了並行執行過程中,資料是如何分發的。通過對DFO,table queue的描述,我闡述生產者-‐消費者模型的工作原理和通訊過程,或許有些描述對你來說過於突然,不用擔心,後面的章節我會通過更多的例子來輔助理解。
如何閱讀並行執行計劃
Table queue 的編號代表了並行執行計劃中,資料分發的順序。理解執行計劃中的並行操作是如何被執行的,原則很簡單:跟隨Tablequeue的順序。通過sqlmonitor報告判斷sql的執行順序,需要結合name列的tablequeue名字比如:TQ10000(代表DFO=1,tablequeue0),:TQ10001(代表DFO=1,tablequeue1),還有PX程序的顏色,進行確定。
下面的例子為dbms_xplan。display_cursor 的輸出。對於並行執行計劃,會多出來三列:
1. TQ列:為Q1:00或者Q1:01,其中Q1代表第一個DFO,00或者01代表tablequeue的編號。
a. ID7~9的操作的TQ列為Q1,00,該組PX程序,作為生產者首先執行,然後通過broadcast 的分發方式,把資料發給消費者。
b. ID10~11,3~6的操作的TQ列為Q1,01,該組PX程序作為消費者接受customer的資料之後,掃描lineorder,hashjoin,聚合之後,又作為生產者通過tablequeue2把資料發給QC。
2. In-‐out 列:表明資料的流動和分發。
•PCWC:parallelcombinewithchild。
•PCWP:parallelcombinewithparent。
•P-‐>P: paralleltoparallel。
•P-‐>S: paralleltoSerial。
3. PQDistribute 列:資料的分發方式。此執行計劃中,我們使用了broadcast 的方式,下面的章節
我會講述其他的分發方式。
HASH分發方式, 兩次資料分發
除了broadcast分發方式,另一種常見的並行分發方式為hash。為了觀察使用hash分發時sql的 執行情況,我對sql使用pq_distributehint。
[js] view plaincopyprint?- select /*+ monitor parallel(4)
- leading(customer lineorder)
- use_hash(lineorder)
- pq_distribute(lineorder hash hash) */
- sum(lo_revenue)
- from
- lineorder, customer
- where
- lo_custkey = c_custkey;
select /*+ monitor parallel(4) leading(customer lineorder) use_hash(lineorder) pq_distribute(lineorder hash hash) */ sum(lo_revenue) from lineorder, customer where lo_custkey = c_custkey;
使用hash分發方式時,sql的執行時間為29s,dbtime為2.6m。相對於broadcast方式,sql的執行時間和dbtime都增加了大約40%。
執行計劃如下,執行計劃為14行,增加了對lineorder的hash分發,第11行的’PXSENDHASH’對3億行資料通過hash函式分發,第10行的’PXRECEIVE’通過tablequeue1接收3億行資料,這兩個操作消耗了38%的dbcpu。這就是為什麼SQL執行時間和dbtime變長的原因。此時,SQL的執行順序為:
- 紅色的PX程序作為生產者,並行掃描customer(第8~9行),對於連線鍵c_custkey運用函式,根據每行記錄的hash值,通過tablequeue0,發給4個藍色消費者的其中一個(第7行)。Hash分發方式並不會複製資料,sqlmonitor報告的第6~9行,actualrows列都為1.5m。
- 紅色的PX程序作為生產者,並行掃描li neorder(第12~13行),對於連線鍵lo_custkey運用同樣的dhash函式,通過tablequeue1,發給4個藍色消費者的其中一個(第11行)。同樣的hash函式保證了customer和lineorder相同的連線鍵會發給同一個消費者,保證hashjoin結果的正確。因為3億行資料都需要經過hash函式計算,然後分發(這是程序間的通訊,或者需要通過RAC心跳網路通訊),這些巨大的額外開銷,就是增加38%cpu的原因。
-
4個藍色的PX程序作為消費者接收了customer的1.5M行記錄(第 6 行),和lineorder的3億行記錄(第10行),進行hash join(第5行),預聚合(第4行)。
-
4個藍色的PX程序反過來作為生產者,通過table queue2,把聚合的資料發給消費者QC(第3 行和第2行)。由QC對接收到4行記錄做最後的聚合, 然後返回給使用者(第1和0行)。
觀察sql monitor報告中Parallel標籤下的資訊,紅色的px程序為例項1、2上的p002/p003程序,藍色的PX程序為p000/p001程序。作為生產者的紅色PX程序負責掃描事實表lineorder,對3億行資料進行hash分發,佔了超過1/3的db time。
因為涉及3億行資料的分發和接收,作為生產者的紅色PX程序和作為消費者的藍色PX程序需要同時活躍,SQL monitor報告中的activity資訊顯示大部分時間,AAS超過並行度4,意味這兩組PX程序同時工作。不像replicate或者broadcast分發時,AAS為4,只有一組PX程序保持活躍。
- SELECT
- dfo_number, tq_id, server_type, instance, process, num_rows
- FROM
- V$PQ_TQSTAT
- ORDER BY
- dfo_number DESC, tq_id, server_type desc, instance, process;
- DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS
- ---------- ---------- -------------- ---------- --------- ----------
- 1 0 Producer 1 P002 299928364
- 1 0 Producer 1 P003 299954384
- 1 0 Producer 2 P002 300188788
- 1 0 Producer 2 P003 299951708
- 1 0 Consumer 1 P000 300005811
- 1 0 Consumer 1 P001 300005811
- 1 0 Consumer 2 P000 300005811
- 1 0 Consumer 2 P001 300005811
- 1 1 Producer 1 P000 1
- 1 1 Producer 1 P001 1
- 1 1 Producer 2 P000 1
- 1 1 Producer 2 P001 1
- 1 1 Consumer 1 QC 4
- 13 rows selected.
SELECT dfo_number, tq_id, server_type, instance, process, num_rows FROM V$PQ_TQSTAT ORDER BY dfo_number DESC, tq_id, server_type desc, instance, process; DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS ---------- ---------- -------------- ---------- --------- ---------- 1 0 Producer 1 P002 299928364 1 0 Producer 1 P003 299954384 1 0 Producer 2 P002 300188788 1 0 Producer 2 P003 299951708 1 0 Consumer 1 P000 300005811 1 0 Consumer 1 P001 300005811 1 0 Consumer 2 P000 300005811 1 0 Consumer 2 P001 300005811 1 1 Producer 1 P000 1 1 1 Producer 1 P001 1 1 1 Producer 2 P000 1 1 1 Producer 2 P001 1 1 1 Consumer 1 QC 4 13 rows selected.[js] view plaincopyprint?
- select /*+ monitor parallel(4)*/
- sum(lo1.lo_revenue)
- from
- lineorder_hash32 lo1, lineorder_hash32 lo2
- where
- lo1.lo_orderkey = lo2.lo_orderkey;
select /*+ monitor parallel(4)*/ sum(lo1.lo_revenue) from lineorder_hash32 lo1, lineorder_hash32 lo2 where lo1.lo_orderkey = lo2.lo_orderkey;
並行查詢之後,通過檢視V$PQ_TQSTAT,進一步驗證以上描述的執行過程。並行執行過程涉及3
個tablequeue0/1/2,V$PQ_TQSTAT包含21行記錄。
1. 例項1、2上的p002/p003程序作為生產者,平均掃描customer的1/4記錄,然後通過tablequeue0(TQ_ID=0),發給作為消費者的p000/p001程序。傳送和接收的customer記錄之和都為 1.5m。
• 傳送的記錄數:1500000= 365658+364899+375679+393764
• 接收的記錄數:1500000= 374690+374924+375709+374677
2. 例項1、2上的p002/p0003程序作為生產者,平均掃描lineorder的1/4記錄,通過table queue1(TQ_ID=1) ,發給作為消費者的p000/p001程序。傳送和接收的lineorder 記錄之和都為300005811。
• 傳送的記錄數:300005811= 74987629+75053393+74979748+74985041
• 接收的記錄數:300005811= 74873553+74968719+75102151+75061388
3. 例項1、2上的p000/p0001程序作為生產者,通過tablequeue2(TQ_ID=2),把聚合的一條結果記 錄發給作為消費者的QC。QC作為消費者,接收了4行記錄。
- SELECT
- dfo_number, tq_id, server_type, instance, process, num_rows
- FROM
- V$PQ_TQSTAT
- ORDER BY
- dfo_number DESC, tq_id, server_type desc, instance, process;
- DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS
- ---------- ---------- ---------------- ---------- --------- ----------
- 1 0 Producer 1 P002 365658
- 1 0 Producer 1 P003 364899
- 1 0 Producer 2 P002 375679
- 1 0 Producer 2 P003 393764
- 1 0 Consumer 1 P000 374690
- 1 0 Consumer 1 P001 374924
- 1 0 Consumer 2 P000 375709
- 1 0 Consumer 2 P001 374677
- 1 1 Producer 1 P002 74987629
- 1 1 Producer 1 P003 75053393
- 1 1 Producer 2 P002 74979748
- 1 1 Producer 2 P003 74985041
- 1 1 Consumer 1 P000 74873553
- 1 1 Consumer 1 P001 74968719
- 1 1 Consumer 2 P000 75102151
- 1 1 Consumer 2 P001 75061388
- 1 2 Producer 1 P000 1
- 1 2 Producer 1 P001 1
- 1 2 Producer 2 P000 1
- 1 2 Producer 2 P001 1
- 1 2 Consumer 1 QC 4
- 21 rows selected.
SELECT dfo_number, tq_id, server_type, instance, process, num_rows FROM V$PQ_TQSTAT ORDER BY dfo_number DESC, tq_id, server_type desc, instance, process; DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS ---------- ---------- ---------------- ---------- --------- ---------- 1 0 Producer 1 P002 365658 1 0 Producer 1 P003 364899 1 0 Producer 2 P002 375679 1 0 Producer 2 P003 393764 1 0 Consumer 1 P000 374690 1 0 Consumer 1 P001 374924 1 0 Consumer 2 P000 375709 1 0 Consumer 2 P001 374677 1 1 Producer 1 P002 74987629 1 1 Producer 1 P003 75053393 1 1 Producer 2 P002 74979748 1 1 Producer 2 P003 74985041 1 1 Consumer 1 P000 74873553 1 1 Consumer 1 P001 74968719 1 1 Consumer 2 P000 75102151 1 1 Consumer 2 P001 75061388 1 2 Producer 1 P000 1 1 2 Producer 1 P001 1 1 2 Producer 2 P000 1 1 2 Producer 2 P001 1 1 2 Consumer 1 QC 4 21 rows selected.
小結
陣列大小m,可以把錯誤判斷的機率控制在很小的範圍之內。我們觀察hash分發時sql的並行執行過程。Hash分發與broadcast最大的區分在於對hashjoin的兩邊都進行分發。這個例子中,對lineorder的hash分發會增加明顯的dbcpu。下一節,我將使用另一個例子,說明hash分發適用的場景。
Replicate,Broadcast和Hash的選擇
我們已經測試過replicate,broadcast,和hash這三種分發方式。
- Replicate :每個PX程序重複掃描hashjoin的左邊,buffercache被用來快取hashjoin左邊的小表,減少重複掃描所需的物理讀。相對於broadcast分發,replicate方式只需一組PX程序。但是replicate不能替換broadcast分發。因為repli cate僅限於hashjoin左邊是表的情況,如果 hashjoin的左邊的結果集來自其他操作,比如join或者檢視,那麼此時無法使用replicate。
-
Broadcast分發:作為生產者的PX程序通過廣播的方式,把hashjoin左邊的結果集分發給每 個作為消費者的PX程序。一般適用於hashjoin左邊結果集比右邊小得多的場景,比如星型模型。
-
Hash分發的本質:把hashjoin的左邊和右邊(兩個資料來源),通過同樣hash函式重新分發,切 分為N個工作單元(假設DoP=N),再進行join ,目的是減少PX程序進行join操作時,需要連線的資料量。Hash分發的代價需要對hashjoin的兩邊都進行分發。對於customer連線lineorder的例子,因為維度表customer的資料量比事實表lineorder小得多,對customer進行replicate或者broadcast分發顯然是更好的選擇,因為這兩種方式不用對lineorder進行重新分發。如果是兩個大表join的話,join操作會是整個執行計劃的瓶頸所在,hash分發是唯一合適的方式。為了減低join的代價,對hashjoin左邊和右邊都進行hash分發的代價是可以接受的。
Hash分發,有時是唯一合理的選擇
我們使用lineorder上的自連線來演示,為什麼有時hash分發是唯一合理的選擇。測試的SQL如 下:
[js] view plaincopyprint?- select /*+ monitor parallel(4)*/
- sum(lo1.lo_revenue)
- from
- lineorder lo1, lineorder lo2
- where
- lo1.lo_orderkey = lo2.lo_orderkey;
select /*+ monitor parallel(4)*/ sum(lo1.lo_revenue) from lineorder lo1, lineorder lo2 where lo1.lo_orderkey = lo2.lo_orderkey;
SQL執行時間為2.4分鐘,dbtime為10.5分鐘。
優化器預設選擇hash分發方式,執行計劃為14行,結構與之前的Hash分發的例子是一致的。不 同的是,第5行的hash join消耗了73%的db time,使用了9GB的臨時表空間,表空間的IO佔12%的db time。大約15%的db time用於Lineorder的兩次hash分發和接收,相對上一個例子的佔38%比例,這兩次HASH分發的整體影響降低了一倍多。
紅色的PX程序為例項1、2上的p002/p003程序,藍色的PX程序為p000/p001程序。作為生產者的紅色PX程序佔總db time的15%左右。
SQL執行開始,對lineorder兩次hash分發時,AAS大於4,分發完成之後,只有藍色的PX程序進行 hash join操作,AAS=4。
從V$PQ_TQSTAT檢視可以確認,對於lineorder的存在兩次分發,通過table queue0和1,作為消費者的4個PX程序接收到的兩次資料是一樣的,保證重新分發不會影響join結果的正確性。每個藍色PX 程序需要hash join的左邊和右邊均為3億行資料的1/4,通過hash分發,3億行記錄連線3億行記錄的工作平均的分配四個獨立PX程序各自處理,每個PX程序處理75M行記錄連線75M行記錄。
[js] view plaincopyprint?- SELECT
- dfo_number, tq_id, server_type, instance, process, num_rows
- FROM
- V$PQ_TQSTAT
- ORDER BY
- dfo_number DESC, tq_id, server_type desc, instance, process;
- DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS
- ---------- ---------- -------------- ---------- --------- ----------
- 1 0 Producer 1 P002 75055725
- 1 0 Producer 1 P003 74977459
- 1 0 Producer 2 P002 74995276
- 1 0 Producer 2 P003 74977351
- 1 0 Consumer 1 P000 74998419
- 1 0 Consumer 1 P001 74995836
- 1 0 Consumer 2 P000 74976974
- 1 0 Consumer 2 P001 75034582
- 1 1 Producer 1 P002 74986798
- 1 1 Producer 1 P003 74985268
- 1 1 Producer 2 P002 74984883
- 1 1 Producer 2 P003 75048862
- 1 1 Consumer 1 P000 74998419
- 1 1 Consumer 1 P001 74995836
- 1 1 Consumer 2 P000 74976974
- 1 1 Consumer 2 P001 75034582
- 1 2 Producer 1 P000 1
- 1 2 Producer 1 P001 1
- 1 2 Producer 2 P000 1
- 1 2 Producer 2 P001 1
- 1 2 Consumer 1 QC 4
- 21 rows selected.
SELECT dfo_number, tq_id, server_type, instance, process, num_rows FROM V$PQ_TQSTAT ORDER BY dfo_number DESC, tq_id, server_type desc, instance, process; DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS ---------- ---------- -------------- ---------- --------- ---------- 1 0 Producer 1 P002 75055725 1 0 Producer 1 P003 74977459 1 0 Producer 2 P002 74995276 1 0 Producer 2 P003 74977351 1 0 Consumer 1 P000 74998419 1 0 Consumer 1 P001 74995836 1 0 Consumer 2 P000 74976974 1 0 Consumer 2 P001 75034582 1 1 Producer 1 P002 74986798 1 1 Producer 1 P003 74985268 1 1 Producer 2 P002 74984883 1 1 Producer 2 P003 75048862 1 1 Consumer 1 P000 74998419 1 1 Consumer 1 P001 74995836 1 1 Consumer 2 P000 74976974 1 1 Consumer 2 P001 75034582 1 2 Producer 1 P000 1 1 2 Producer 1 P001 1 1 2 Producer 2 P000 1 1 2 Producer 2 P001 1 1 2 Consumer 1 QC 4 21 rows selected.
使用 broadcast 分發,糟糕的效能
對於lineorder,lineorder的自連線, 如果我們使用broadcast分發,會出現什麼情況呢?我們測試一下:
[js] view plaincopyprint?- select /*+ monitor parallel(4)
- leading(lo1 lo2)
- use_hash(lo2)
- pq_distribute(lo2 broadcast none) */
- 15
- sum(lo1.lo_revenue)
- from
- lineorder lo1, lineorder lo2
- where
- lo1.lo_orderkey = lo2.lo_orderkey;
select /*+ monitor parallel(4) leading(lo1 lo2) use_hash(lo2) pq_distribute(lo2 broadcast none) */ 15 sum(lo1.lo_revenue) from lineorder lo1, lineorder lo2 where lo1.lo_orderkey = lo2.lo_orderkey;
使用broadcase分發,SQL的執行時間為5.9分鐘,db time為23.8分鐘。相比hash分發,執行時間和 db time都增加了接近1.5倍。
紅色的PX程序作為生產者,對lineorder進行並行掃描之後,3億行記錄通過tablequeue0廣播給4個作為消費者的藍色PX程序(第6~9行),相當於複製了4份,每個藍色的PX程序都接收了3億行記錄.這次broadcast分發消耗了11%的db time,因為需要每行記錄傳輸給每個藍色PX程序,消耗的db cpu比使用hash分發時兩次hash分發所消耗的還多。
第5行的hash join的所消耗的臨時表空間上升到27GB,臨時表空間IO佔的db time的38%。因為每個藍色PX程序進行hash join的資料變大了,hash join的左邊為3億行資料,hash join的右邊為3億行記錄的1/4.
藍色PX程序為消費者負責hash join,所消耗的db time都大幅增加了。
hash join時,臨時表空間讀等待事件’direct path read temp’明顯增加了。
V$PQ_TQSTAT的輸出中,例項1、2上的p000/p001程序作為消費者,都接收了3億行資料,造成後續hash join的急劇變慢。Broadcast分發對hash join左邊進行廣播的機制,決定了它不適合hash join兩邊都為大表的情況。
- SELECT
- dfo_number, tq_id, server_type, instance, process, num_rows
- FROM
- V$PQ_TQSTAT
- ORDER BY
- dfo_number DESC, tq_id, server_type desc, instance, process;
- DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS
- ---------- ---------- -------------- ---------- --------- ----------
- 1 0 Producer 1 P002 299928364
- 1 0 Producer 1 P003 299954384
- 1 0 Producer 2 P002 300188788
- 1 0 Producer 2 P003 299951708
- 1 0 Consumer 1 P000 300005811
- 1 0 Consumer 1 P001 300005811
- 1 0 Consumer 2 P000 300005811
- 1 0 Consumer 2 P001 300005811
- 1 1 Producer 1 P000 1
- 1 1 Producer 1 P001 1
- 1 1 Producer 2 P000 1
- 1 1 Producer 2 P001 1
- 1 1 Consumer 1 QC 4
- 13 rows selected.
SELECT dfo_number, tq_id, server_type, instance, process, num_rows FROM V$PQ_TQSTAT ORDER BY dfo_number DESC, tq_id, server_type desc, instance, process; DFO_NUMBER TQ_ID SERVER_TYPE INSTANCE PROCESS NUM_ROWS ---------- ---------- -------------- ---------- --------- ---------- 1 0 Producer 1 P002 299928364 1 0 Producer 1 P003 299954384 1 0 Producer 2 P002 300188788 1 0 Producer 2 P003 299951708 1 0 Consumer 1 P000 300005811 1 0 Consumer 1 P001 300005811 1 0 Consumer 2 P000 300005811 1 0 Consumer 2 P001 300005811 1 1 Producer 1 P000 1 1 1 Producer 1 P001 1 1 1 Producer 2 P000 1 1 1 Producer 2 P001 1 1 1 Consumer 1 QC 4 13 rows selected.
小結,Broadcast和Hash分發的陷阱
通過前一節和本節的例子