碼農必備SQL調優(上)

語言: CN / TW / HK

目錄

Query執行流程

SQL執行計劃介紹

SQL執行計劃概述

詳解

 調優流程

 更新統計資訊

審視和修改表定義

審視和修改表定義概述

選擇儲存模型

使用區域性聚簇

使用分割槽表

選擇資料型別

典型SQL調優點

SQL自診斷

子查詢調優

統計資訊調優

運算元級調優

經驗總結:SQL語句改寫規則

SQL調優關鍵引數調整

Query執行流程

SQL引擎從接受SQL語句到執行SQL語句需要經歷的步驟如圖1和表1所示。其中,紅色字型部分為DBA可以介入實施調優的環節。

圖 1 SQL引擎執行查詢類SQL語句的流程

表 1 SQL引擎執行查詢類SQL語句的步驟說明

步驟

說明

1、語法&詞法解析

按照約定的SQL語句規則,把輸入的SQL語句從字串轉化為格式化結構(Stmt)。

2、語義解析

將“語法&詞法解析”輸出的格式化結構轉化為資料庫可以識別的物件。

3、查詢重寫

根據規則把“語義解析”的輸出等價轉化為執行上更為優化的結構。

4、查詢優化

根據“查詢重寫”的輸出和資料庫內部的統計資訊規劃SQL語句具體的執行方式,也就是執行計劃。統計資訊和GUC引數對查詢優化(執行計劃)的影響,請參見調優手段之統計資訊調優手段之GUC引數

5、查詢執行

根據“查詢優化”規劃的執行路徑執行SQL查詢語句。底層儲存方式的選擇合理性,將影響查詢執行效率。詳見調優手段之底層儲存

調優手段之統計資訊

openGauss優化器是典型的基於代價的優化(Cost-Based Optimization,簡稱CBO)。在這種優化器模型下,資料庫根據表的元組數、欄位寬度、NULL記錄比率、distinct值、MCV值、HB值等表的特徵值,以及一定的代價計算模型,計算出每一個執行步驟的不同執行方式的輸出元組數和執行代價(cost),進而選出整體執行代價最小/首元組返回代價最小的執行方式進行執行。這些特徵值就是統計資訊。從上面的描述可以看出統計資訊是查詢優化的核心輸入,準確的統計資訊將幫助規劃器選擇最合適的查詢規劃。一般來說我們會通過ANALYZE語法收集整個表或者表的若干個欄位的統計資訊,週期性地執行ANALYZE,或者在對錶的大部分內容做了更改之後馬上執行ANALYZE。

調優手段之GUC引數

查詢優化的主要目的是為查詢語句選擇高效的執行方式。

如下SQL語句:

select count(1) 
from customer inner join store_sales on (ss_customer_sk = c_customer_sk);

在執行customer inner join store_sales的時候,openGauss支援Nested Loop、Merge Join和Hash Join三種不同的Join方式。優化器會根據表customer和表store_sales的統計資訊估算結果集的大小以及每種join方式的執行代價,然後對比選出執行代價最小的執行計劃。

正如前面所說,執行代價計算都是基於一定的模型和統計資訊進行估算,當因為某些原因代價估算不能反映真實的cost的時候,我們就需要通過guc引數設定的方式讓執行計劃傾向更優規劃。

調優手段之底層儲存

openGauss的表支援行存表、列存表,底層儲存方式的選擇嚴格依賴於客戶的具體業務場景。一般來說計算型業務查詢場景(以關聯、聚合操作為主)建議使用列存表;點查詢、大批量UPDATE/DELETE業務場景適合行存表。

對於每種儲存方式還有對應的儲存層優化手段,這部分會在後續的調優章節深入介紹。

調優手段之SQL重寫

除了上述干預SQL引擎所生成執行計劃的執行效能外,根據資料庫的SQL執行機制以及大量的實踐發現,有些場景下,在保證客戶業務SQL邏輯的前提下,通過一定規則由DBA重寫SQL語句,可以大幅度的提升SQL語句的效能。

這種調優場景對DBA的要求比較高,需要對客戶業務有足夠的瞭解,同時也需要紮實的SQL語句基本功,後續會介紹幾個常見的SQL改寫場景。

SQL執行計劃介紹

SQL執行計劃概述

SQL執行計劃是一個節點樹,顯示openGauss執行一條SQL語句時執行的詳細步驟。每一個步驟為一個數據庫運算子。

使用EXPLAIN命令可以檢視優化器為每個查詢生成的具體執行計劃。EXPLAIN給每個執行節點都輸出一行,顯示基本的節點型別和優化器為執行這個節點預計的開銷值。如圖1所示。

圖 1 SQL執行計劃示例

  • 最底層節點是表掃描節點,它掃描表並返回原始資料行。不同的表訪問模式有不同的掃描節點型別:順序掃描、索引掃描等。最底層節點的掃描物件也可能是非錶行資料(不是直接從表中讀取的資料),如VALUES子句和返回行集的函式,它們有自己的掃描節點型別。
  • 如果查詢需要連線、聚集、排序、或者對原始行做其它操作,那麼就會在掃描節點之上新增其它節點。 並且這些操作通常都有多種方法,因此在這些位置也有可能出現不同的執行節點型別。
  • 第一行(最上層節點)是執行計劃總執行開銷的預計。這個數值就是優化器試圖最小化的數值。

執行計劃顯示資訊

除了設定不同的執行計劃顯示格式外,還可以通過不同的EXPLAIN用法,顯示不同詳細程度的執行計劃資訊。常見有如下幾種,關於更多用法請參見EXPLAIN語法說明。

  • EXPLAIN _statement_:只生成執行計劃,不實際執行。其中statement代表SQL語句。
  • EXPLAIN ANALYZE _statement_:生成執行計劃,進行執行,並顯示執行的概要資訊。顯示中加入了實際的執行時間統計,包括在每個規劃節點內部花掉的總時間(以毫秒計)和它實際返回的行數。
  • EXPLAIN PERFORMANCE _statement_:生成執行計劃,進行執行,並顯示執行期間的全部資訊。

為了測量執行時在執行計劃中每個節點的開銷,EXPLAIN ANALYZE或EXPLAIN PERFORMANCE會在當前查詢執行上增加效能分析的開銷。在一個查詢上執行EXPLAIN ANALYZE或EXPLAIN PERFORMANCE有時會比普通查詢明顯的花費更多的時間。超支的數量依賴於查詢的本質和使用的平臺。

因此,當定位SQL執行慢問題時,如果SQL長時間執行未結束,建議通過EXPLAIN命令檢視執行計劃,進行初步定位。如果SQL可以執行出來,則推薦使用EXPLAIN ANALYZE或EXPLAIN PERFORMANCE檢視執行計劃及其實際的執行資訊,以便更精準地定位問題原因。

EXPLAIN PERFORMANCE輕量化執行方式與EXPLAIN PERFORMANCE保持一致,在原來的基礎上減少了效能分析的時間,執行時間與SQL執行時間的差異顯著減少。

詳解

如SQL執行計劃概述節中所說,EXPLAIN會顯示執行計劃,但並不會實際執行SQL語句。EXPLAIN ANALYZE和EXPLAIN PERFORMANCE兩者都會實際執行SQL語句並返回執行資訊。在這一節將詳細解釋執行計劃及執行資訊。

執行計劃

以如下SQL語句為例:

SELECT * FROM t1, t2 WHERE t1.c1 = t2.c2;

執行EXPLAIN的輸出為:

執行計劃層級解讀(縱向):

  1. 第一層:Seq Scan on t2

    表掃描運算元,用Seq Scan的方式掃描表t2。這一層的作用是把表t2的資料從buffer或者磁碟上讀上來輸送給上層節點參與計算。

  2. 第二層:Hash

    Hash運算元,作用是把下層計算輸送上來的運算元計算hash值,為後續hash join操作做資料準備。

  3. 第三層:Seq Scan on t1

    表掃描運算元,用Seq Scan的方式掃描表t1。這一層的作用是把表t1的資料從buffer或者磁碟上讀上來輸送給上層節點參與hash join計算。

  4. 第四層:Hash Join

    join運算元,主要作用是將t1表和t2表的資料通過hash join的方式連線,並輸出結果資料。

執行計劃中的關鍵字說明:

  1. 表訪問方式

    • Seq Scan

      全表順序掃描。

    • Index Scan

      優化器決定使用兩步的規劃:最底層的規劃節點訪問一個索引,找出匹配索引條件的行的位置,然後上層規劃節點真實地從表中抓取出那些行。獨立地抓取資料行比順序地讀取它們的開銷高很多,但是因為並非所有表的頁面都被訪問了,這麼做實際上仍然比一次順序掃描開銷要少。使用兩層規劃的原因是,上層規劃節點在讀取索引標識出來的行位置之前,會先將它們按照物理位置排序,這樣可以最小化獨立抓取的開銷。

      如果在WHERE裡面使用的好幾個欄位上都有索引,那麼優化器可能會使用索引的AND或OR的組合。但是這麼做要求訪問兩個索引,因此與只使用一個索引,而把另外一個條件只當作過濾器相比,這個方法未必是更優。

      索引掃描可以分為以下幾類,他們之間的差異在於索引的排序機制。

      • Bitmap Index Scan

        使用點陣圖索引抓取資料頁。

      • Index Scan using index_name

        使用簡單索引搜尋,該方式表的資料行是以索引順序抓取的,這樣就令讀取它們的開銷更大,但是這裡的行少得可憐,因此對行位置的額外排序並不值得。最常見的就是看到這種規劃型別只抓取一行,以及那些要求ORDER BY條件匹配索引順序的查詢。因為那時候沒有多餘的排序步驟是必要的以滿足ORDER BY。

  2. 表連線方式

    • Nested Loop

      巢狀迴圈,適用於被連線的資料子集較小的查詢。在巢狀迴圈中,外表驅動內表,外表返回的每一行都要在內表中檢索找到它匹配的行,因此整個查詢返回的結果集不能太大(不能大於10000),要把返回子集較小的表作為外表,而且在內表的連線欄位上建議要有索引。

    • (Sonic) Hash Join

      雜湊連線,適用於資料量大的表的連線方式。優化器使用兩個表中較小的表,利用連線鍵在記憶體中建立hash表,然後掃描較大的表並探測雜湊,找到與雜湊匹配的行。Sonic和非Sonic的Hash Join的區別在於所使用hash表結構不同,不影響執行的結果集。

    • Merge Join

      歸併連線,通常情況下執行效能差於雜湊連線。如果源資料已經被排序過,在執行融合連線時,並不需要再排序,此時融合連線的效能優於雜湊連線。

  3. 運算子

    • sort

      對結果集進行排序。

    • filter

      EXPLAIN輸出顯示WHERE子句當作一個“filter”條件附屬於順序掃描計劃節點。這意味著規劃節點為它掃描的每一行檢查該條件,並且只輸出符合條件的行。預計的輸出行數降低了,因為有WHERE子句。不過,掃描仍將必須訪問所有 10000 行,因此開銷沒有降低;實際上它還增加了一些(確切的說,通過10000 * cpu_operator_cost)以反映檢查WHERE條件的額外CPU時間。

    • LIMIT

      LIMIT限定了執行結果的輸出記錄數。如果增加了LIMIT,那麼不是所有的行都會被檢索到。

執行資訊

以如下SQL語句為例:

複製程式碼select sum(t2.c1) from t1,t2 where t1.c1=t2.c2 group by t1.c2;

執行EXPLAIN PERFORMANCE輸出為:

 調優流程

對慢SQL語句進行分析,通常包括以下步驟:

操作步驟

  1. 收集SQL中涉及到的所有表的統計資訊。在資料庫中,統計資訊是規劃器生成計劃的源資料。沒有收集統計資訊或者統計資訊陳舊往往會造成執行計劃嚴重劣化,從而導致效能問題。從經驗資料來看,10%左右效能問題是因為沒有收集統計資訊。具體請參見更新統計資訊
  2. 通過檢視執行計劃來查詢原因。如果SQL長時間執行未結束,通過EXPLAIN命令檢視執行計劃,進行初步定位。如果SQL可以執行出來,則推薦使用EXPLAIN ANALYZE或EXPLAIN PERFORMANCE檢視執行計劃及實際執行情況,以便更精準地定位問題原因。有關執行計劃的詳細介紹請參見SQL執行計劃介紹
  3. 審視和修改表定義
  4. 針對EXPLAIN或EXPLAIN PERFORMANCE資訊,定位SQL慢的具體原因以及改進措施,具體參見典型SQL調優點
  5. 通常情況下,有些SQL語句可以通過查詢重寫轉換成等價的,或特定場景下等價的語句。重寫後的語句比原語句更簡單,且可以簡化某些執行步驟達到提升效能的目的。查詢重寫方法在各個資料庫中基本是通用的。經驗總結:SQL語句改寫規則介紹了幾種常用的通過改寫SQL進行調優的方法。

 更新統計資訊

 在資料庫中,統計資訊是規劃器生成計劃的源資料。沒有收集統計資訊或者統計資訊陳舊往往會造成執行計劃嚴重劣化,從而導致效能問題。

 背景資訊

ANALYZE語句可收集與資料庫中表內容相關的統計資訊,統計結果儲存在系統表PG_STATISTIC中。查詢優化器會使用這些統計資料,以生成最有效的執行計劃。

建議在執行了大批量插入/刪除操作後,例行對錶或全庫執行ANALYZE語句更新統計資訊。目前預設收集統計資訊的取樣比例是30000行(即:GUC引數default_statistics_target預設設定為100),如果表的總行數超過一定行數(大於1600000),建議設定Guc引數default_statistics_target為-2,即按2%收集樣本估算統計資訊。

對於在批處理指令碼或者儲存過程中生成的中間表,也需要在完成資料生成之後顯式的呼叫ANALYZE。

對於表中多個列有相關性且查詢中有同時基於這些列的條件或分組操作的情況,可嘗試收集多列統計資訊,以便查詢優化器可以更準確地估算行數,並生成更有效的執行計劃。

操作步驟

使用以下命令更新某個表或者整個database的統計資訊。
--更新單個表的統計資訊。 ANALYZE tablename; --更新全庫的統計資訊。 ANALYZE;

使用以下命令進行多列統計資訊相關操作。

 --收集tablename表的column_1、column_2列的多列統計資訊。
ANALYZE tablename ((column_1, column_2));        

--新增tablename表的column_1、column_2列的多列統計資訊宣告。
ALTER TABLE tablename ADD STATISTICS ((column_1, column_2));    

--收集單列統計資訊,並收集已宣告的多列統計資訊。
ANALYZE tablename;        

 --刪除tablename表的column_1、column_2列的多列統計資訊或其宣告。
ALTER TABLE tablename DELETE STATISTICS ((column_1, column_2));

 須知:
在使用ALTER TABLE tablename ADD STATISTICS語句添加了多列統計資訊聲明後,系統並不會立刻收集多列統計資訊,而是在下次對該表或全庫進行ANALYZE時,進行多列統計資訊的收集。
如果想直接收集多列統計資訊,請使用ANALYZE命令進行收集。

 說明:
使用EXPLAIN檢視各SQL的執行計劃時,如果發現某個表SEQ SCAN的輸出中rows=10,rows=10是系統給的預設值,有可能該表沒有進行ANALYZE,需要對該表執行ANALY

審視和修改表定義

審視和修改表定義概述

好的表定義至少需要達到以下幾個目標:

  1. 減少掃描資料資料量。通過分割槽的剪枝機制可以實現該點。
  2. 儘量極少隨機IO。通過聚簇/區域性聚簇可以實現該點。

表定義在資料庫設計階段建立,在SQL調優過程中進行審視和修改。

選擇儲存模型

進行資料庫設計時,表設計上的一些關鍵項將嚴重影響後續整庫的查詢效能。表設計對資料儲存也有影響:好的表設計能夠減少I/O操作及最小化記憶體使用,進而提升查詢效能。

表的儲存模型選擇是表定義的第一步。客戶業務屬性是表的儲存模型的決定性因素,依據下面表格選擇適合當前業務的儲存模型。

儲存模型

適用場景

行存

點查詢(返回記錄少,基於索引的簡單查詢)。

增刪改比較多的場景。

列存

統計分析類查詢(group 、join多的場景)。

使用區域性聚簇

區域性聚簇(Partial Cluster Key)是列存下的一種技術。這種技術可以通過min/max稀疏索引較快的實現基表掃描的filter過濾。Partial Cluster Key可以指定多列,但是一般不建議超過2列。Partial Cluster Key的選取原則:

  1. 受基表中的簡單表示式約束。這種約束一般形如col op const,其中col為列名,op為操作符 =、>、>=、<=、<,const為常量值。
  2. 儘量採用選擇度比較高(過濾掉更多資料)的簡單表示式中的列。
  3. 儘量把選擇度比較低的約束col放在Partial Cluster Key中的前面。
  4. 儘量把列舉型別的列放在Partial Cluster Key中的前面。

使用分割槽表

分割槽表是把邏輯上的一張表根據某種方案分成幾張物理塊進行儲存。這張邏輯上的表稱之為分割槽表,物理塊稱之為分割槽。分割槽表是一張邏輯表,不儲存資料,資料實際是儲存在分割槽上的。分割槽表和普通表相比具有以下優點:

  1. 改善查詢效能:對分割槽物件的查詢可以僅搜尋自己關心的分割槽,提高檢索效率。
  2. 增強可用性:如果分割槽表的某個分割槽出現故障,表在其他分割槽的資料仍然可用。
  3. 方便維護:如果分割槽表的某個分割槽出現故障,需要修復資料,只修復該分割槽即可。

openGauss支援的分割槽表為範圍分割槽表。

範圍分割槽表:將資料基於範圍對映到每一個分割槽。這個範圍是由建立分割槽表時指定的分割槽鍵決定的。分割槽鍵經常採用日期,例如將銷售資料按照月份進行分割槽。

選擇資料型別

高效資料型別,主要包括以下三方面:

  1. 儘量使用執行效率比較高的資料型別

    一般來說整型資料運算(包括=、>、<、≧、≦、≠等常規的比較運算,以及group by)的效率比字串、浮點數要高。比如某客戶場景中對列存表進行點查詢,filter條件在一個numeric列上,執行時間為10+s;修改numeric為int型別之後,執行時間縮短為1.8s左右。

  2. 儘量使用短欄位的資料型別

    長度較短的資料型別不僅可以減小資料檔案的大小,提升IO效能;同時也可以減小相關計算時的記憶體消耗,提升計算效能。比如對於整型資料,如果可以用smallint就儘量不用int,如果可以用int就儘量不用bigint。

  3. 使用一致的資料型別

    表關聯列儘量使用相同的資料型別。如果表關聯列資料型別不同,資料庫必須動態地轉化為相同的資料型別進行比較,這種轉換會帶來一定的效能開銷。

典型SQL調優點

SQL調優是一個不斷分析與嘗試的過程:試跑Query,判斷效能是否滿足要求;如果不滿足要求,則通過檢視執行計劃分析原因並進行鍼對性優化;然後重新試跑和優化,直到滿足效能目標。

SQL自診斷

使用者在執行查詢或者執行INSERT/DELETE/UPDATE/CREATE TABLE AS語句時,可能會遇到效能問題。這種情況下,通過查詢PG_CONTROL_GROUP_CONFIGGS_SESSION_MEMORY_DETAIL檢視的warning欄位可以獲得對應查詢可能導致效能問題的告警資訊,為效能調優提供參考。

SQL自診斷的告警型別與resource_track_level的設定有關係。如果resource_track_level設定為query,則可以診斷多列/單列統計資訊未收集和SQL不下推的告警。如果resource_track_level設定為operator,則可以診斷所有的告警場景。

SQL自診斷的診斷範圍與resource_track_cost的設定有關係。當SQL的代價大於resource_track_cost時,SQL才會被診斷。SQL的代價可以通過explain來確認。

告警場景

目前支援對多列/單列統計資訊未收集導致效能問題的場景上報告警。

如果存在單列或者多列統計資訊未收集,則上報相關告警。調優方法可以參考更新統計資訊統計資訊調優

告警資訊示例:

整表的統計資訊未收集:

Statistic Not Collect:
    schema_test.t1

單列統計資訊未收集:

Statistic Not Collect:
    schema_test.t2(c1,c2)

多列統計資訊未收集:

Statistic Not Collect:
    schema_test.t3((c1,c2))

單列和多列統計資訊未收集:

Statistic Not Collect:
    schema_test.t4(c1,c2)    schema_test.t4((c1,c2))

規格約束

  1. 告警字串長度上限為2048。如果告警資訊超過這個長度(例如存在大量未收集統計資訊的超長表名、列名等資訊)則不告警,只上報warning:

    WARNING, "Planner issue report is truncated, the rest of planner issues will be skipped"
    
  2. 如果query存在limit節點(即查詢語句中包含limit),則不會上報limit節點以下的Operator級別的告警。

子查詢調優

子查詢背景介紹

應用程式通過SQL語句來操作資料庫時會使用大量的子查詢,這種寫法比直接對兩個表做連線操作在結構上和思路上更清晰,尤其是在一些比較複雜的查詢語句中,子查詢有更完整、更獨立的語義,會使SQL對業務邏輯的表達更清晰更容易理解,因此得到了廣泛的應用。

openGauss根據子查詢在SQL語句中的位置把子查詢分成了子查詢、子連結兩種形式。

  • 子查詢SubQuery:對應於查詢解析樹中的範圍表RangeTblEntry,更通俗一些指的是出現在FROM語句後面的獨立的SELECT語句。
  • 子連結SubLink:對應於查詢解析樹中的表示式,更通俗一些指的是出現在where/on子句、targetlist裡面的語句。

    綜上,對於查詢解析樹而言,SubQuery的本質是範圍表,而SubLink的本質是表示式。針對SubLink場景而言,由於SubLink可以出現在約束條件、表示式中,按照openGauss對SubLink的實現,SubLink可以分為以下幾類:

    • exist_sublink:對應EXIST、NOT EXIST語句
    • any_sublink:對應op ALL(select…)語句,其中OP可以是IN、<、>、=操作符
    • all_sublink:對應op ALL(select…)語句,其中OP可以是IN、<、>、=操作符
    • rowcompare_sublink:對應record op (select …)語句
    • expr_sublink:對應(SELECT with single targetlist item …)語句
    • array_sublink:對應ARRAY(select…)語句
    • cte_sublink:對應with query(…)語句

    其中OLAP、HTAP場景中常用的sublink為exist_sublink、any_sublink,在openGauss的優化引擎中對其應用場景做了優化(子連結提升),由於SQL語句中子查詢的使用的靈活性,會帶來SQL子查詢過於複雜造成效能問題。子查詢從大類上來看,分為非相關子查詢和相關子查詢:

    • 非相關子查詢None-Correlated SubQuery

      子查詢的執行不依賴於外層父查詢的任何屬性值。這樣子查詢具有獨立性,可獨自求解,形成一個子查詢計劃先於外層的查詢求解。

      例如:

      select t1.c1,t1.c2
      from t1
      where t1.c1 in (
          select c2
          from t2
          where t2.c2 IN (2,3,4)
      );
                                    QUERY PLAN
      ----------------------------------------------------------------
       Hash Join 
         Hash Cond: (t1.c1 = t2.c2)
         ->  Seq Scan on t1 
               Filter: (c1 = ANY ('{2,3,4}'::integer[]))
         ->  Hash 
               ->  HashAggregate 
                     Group By Key: t2.c2
                     ->  Seq Scan on t2  
                           Filter: (c2 = ANY ('{2,3,4}'::integer[]))
      (9 rows)
              
      
    • 相關子查詢Correlated-SubQuery

      子查詢的執行依賴於外層父查詢的一些屬性值(如下列示例t2.c1 = t1.c1條件中的t1.c1)作為內層查詢的一個AND-ed條件。這樣的子查詢不具備獨立性,需要和外層查詢按分組進行求解。

      例如:

      select t1.c1,t1.c2
      from t1
      where t1.c1 in (
          select c2
          from t2
          where t2.c1 = t1.c1 AND t2.c2 in (2,3,4)
      );
                                     QUERY PLAN
      ------------------------------------------------------------------------
       Seq Scan on t1
         Filter: (SubPlan 1)
         SubPlan 1
           ->  Seq Scan on t2
                 Filter: ((c1 = t1.c1) AND (c2 = ANY ('{2,3,4}'::integer[])))
      (5 rows)
              
      

openGauss對SubLink的優化

針對SubLink的優化策略主要是讓內層的子查詢提升(pullup),能夠和外表直接做關聯查詢,從而避免生成SubPlan+Broadcast內表的執行計劃。判斷子查詢是否存在效能風險,可以通過explain查詢語句檢視Sublink的部分是否被轉換成SubPlan的執行計劃。

例如:

箭頭右側執行計劃應替換成下面的執行計劃:

QUERY PLAN
--------------------------------
Seq Scan on t1
Filter: (SubPlan 1)
SubPlan 1
->  Seq Scan on t2
Filter: (c1 = t1.c1)
(5 rows)
  • 目前openGauss支援的Sublink-Release場景

    • IN-Sublink無相關條件

      • 不能包含上一層查詢的表中的列(可以包含更高層查詢表中的列)。
      • 不能包含易變函式。

      箭頭右側執行計劃應替換成下面的執行計劃:

      QUERY PLAN
      --------------------------------------
      Hash Join
      Hash Cond: (t1.c1 = t2.c2)
      ->  Seq Scan on t1
      ->  Hash
      ->  HashAggregate
      Group By Key: t2.c2
      ->  Seq Scan on t2
      Filter: (c1 = 1)
      (8 rows)
      
    • Exist-Sublink包含相關條件

      Where子句中必須包含上一層查詢的表中的列,子查詢的其它部分不能含有上層查詢的表中的列。其它限制如下。

      • 子查詢必須有from子句。
      • 子查詢不能含有with子句。
      • 子查詢不能含有聚集函式。
      • 子查詢裡不能包含集合操作、排序、limit、windowagg、having操作。
      • 不能包含易變函式。

      箭頭右側執行計劃應替換成下面的執行計劃:

      QUERY PLAN


      Hash Join

      Hash Cond: (t1.c1 = t2.c1)

      -> Seq Scan on t1

      -> Hash

      -> HashAggregate

      Group By Key: t2.c1

      -> Seq Scan on t2

      (7 rows)

    • 包含聚集函式的等值相關子查詢的提升

      子查詢的where條件中必須含有來自上一層的列,而且此列必須和子查詢本層涉及表中的列做相等判斷,且這些條件必須用and連線。其它地方不能包含上層的列。其它限制條件如下。

      • 子查詢中where條件包含的表示式(列名)必須是表中的列。
      • 子查詢的Select關鍵字後,必須有且僅有一個輸出列,此輸出列必須是聚集函式(如max),並且聚集函式的引數(t2.c2)不能是來自外層表(t1)中的列。聚集函式不能是count。

        例如,下列示例可以提升。

        select * from t1 where c1 >(
               select max(t2.c1) from t2 where t2.c1=t1.c1
        );
        

        下列示例不能提升,因為子查詢沒有聚集函式。

        select * from t1 where c1 >(
               select  t2.c1 from t2 where t2.c1=t1.c1
        );
        

        下列示例不能提升,因為子查詢有兩個輸出列。

        select * from t1 where (c1,c2) >(
               select  max(t2.c1),min(t2.c2) from t2 where t2.c1=t1.c1
        );
        
      • 子查詢必須是from子句。

      • 子查詢中不能有groupby、having、集合操作。

      • 子查詢只能是inner join。

        例如:下列示例不能提升。

        select * from t1 where c1 >(
               select max(t2.c1) from t2 full join t3 on (t2.c2=t3.c2) where t2.c1=t1.c1
        );
        
      • 子查詢的targetlist中不能包含返回set的函式。

      • 子查詢的where條件中必須含有來自上一層的列,而且此列必須和子查詢層涉及表中的列做相等判斷,且這些條件必須用and連線。其它地方不能包含上層的上層中的列。例如:下列示例中的最內層子連結可以提升。

        select * from t3 where t3.c1=(
                select t1.c1
                from t1 where c1 >(
                        select max(t2.c1) from t2 where t2.c1=t1.c1 
        ));
        

        基於上面的示例,再加一個條件,則不能提升,因為最內側子查詢引用了上層中的列。示例如下:

        select * from t3 where t3.c1=(
                select t1.c1
                from t1 where c1 >(
                       select max(t2.c1) from t2 where t2.c1=t1.c1 and t3.c1>t2.c2
                    
        ));
        
    • 提升OR子句中的SubLink

      當WHERE過濾條件中有OR連線的EXIST相關SubLink,

      例如:

      select a, c from t1
      where t1.a = (select avg(a) from t3 where t1.b = t3.b) or
      exists (select * from t4 where t1.c = t4.c);
      

      將OR-ed連線的EXIST相關子查詢OR字句的提升過程:

      1. 提取where條件中,or子句中的opExpr。為:t1.a = (select avg(a) from t3 where t1.b = t3.b)
      2. 這個op操作中包含subquery,判斷是否可以提升,如果可以提升,重寫subquery為:select avg(a)、 t3.b from t3 group by t3.b,生成not null條件t3.b is not null,並將這個opexpr用這個not null條件替換。此時SQL變為:

        select a, c
        from t1 left join (select avg(a) avg, t3.b from t3 group by t3.b)  as t3 on (t1.a = avg and t1.b = t3.b)
        where t3.b is not null or exists (select * from t4 where t1.c = t4.c);
        
      3. 再次提取or子句中的exists sublink、exists (select * from t4 where t1.c = t4.c),判斷是否可以提升,如果可以提升,轉換subquery為:select t4.c from t4 group by t4.c生成NotNull條件t4.c is not null提升查詢,SQL變為:

        select a, c
        from t1 left join (select avg(a) avg, t3.b from t3 group by t3.b)  as t3 on (t1.a = avg and t1.b = t3.b)
        left join (select t4.c from t4 group by t4.c) where t3.b is not null or t4.c is not null;
        
  • 目前openGauss不支援的Sublink-Release場景

    除了以上場景之外都不支援Sublink提升,因此關聯子查詢會被計劃成SubPlan+Broadcast的執行計劃,當inner表的資料量較大時則會產生效能風險。

    如果相關子查詢中跟外層的兩張表做join,那麼無法提升該子查詢,需要通過將父SQL建立成with子句,然後再跟子查詢中的表做相關子查詢查詢。

    例如:

    select distinct t1.a, t2.a
    from t1 left join t2 on t1.a=t2.a and not exists (select a,b from test1 where test1.a=t1.a and test1.b=t2.a);
    

    改寫為

    with temp as
    (
            select * from (select t1.a as a, t2.a as b from t1 left join t2 on t1.a=t2.a)
        
    )
    select distinct a,b
    from temp
    where not exists (select a,b from test1 where temp.a=test1.a and temp.b=test1.b);
    
    • 出現在targetlist裡的相關子查詢無法提升(不含count)

      例如:

      explain (costs off)
      select (select c2 from t2 where t1.c1 = t2.c1) ssq, t1.c2
      from t1
      where t1.c2 > 10;
      

      執行計劃為:

      explain (costs off)
      select (select c2 from t2 where t1.c1 = t2.c1) ssq, t1.c2
      from t1
      where t1.c2 > 10;
                 QUERY PLAN
      --------------------------------
       Seq Scan on t1
         Filter: (c2 > 10)
         SubPlan 1
           ->  Seq Scan on t2
                 Filter: (t1.c1 = c1)
      (5 rows)
      

      由於相關子查詢出現在targetlist(查詢返回列表)裡,對於t1.c1=t2.c1不匹配的場景仍然需要輸出值,因此使用left-outerjoin關聯T1&T2確保t1.c1=t2.c1在不匹配時,子SSQ能夠返回不匹配的補空值。

       說明:
      SSQ和CSSQ的解釋如下:
      - SSQ:ScalarSubQuery一般指返回1行1列scalar值的sublink,簡稱SSQ。
      - CSSQ:Correlated-ScalarSubQuery和SSQ相同不過是指包含相關條件的SSQ。

      上述SQL語句可以改寫為:

      with ssq as
      (
          select t2.c2 from t2
      )
      select ssq.c2, t1.c2
      from t1 left join ssq on t1.c1 = ssq.c2
      where t1.c2 > 10;
      

      改寫後的執行計劃為:

                 QUERY PLAN
      ---------------------------------
       Hash Right Join
         Hash Cond: (ssq.c2 = t1.c1)
         CTE ssq
           ->  Seq Scan on t2
         ->  CTE Scan on ssq
         ->  Hash
               ->  Seq Scan on t1
                     Filter: (c2 > 10)
      (8 rows)
      

      可以看到出現在SSQ返回列表裡的相關子查詢SSQ,已經被提升成Right Join,從而避免當內表T2較大時出現SubPlan計劃導致效能變差。

    • 出現在targetlist裡的相關子查詢無法提升(帶count)

      例如:

      select (select count(*) from t2 where t2.c1=t1.c1) cnt, t1.c1, t3.c1
      from t1,t3
      where t1.c1=t3.c1 order by cnt, t1.c1;
      

      執行計劃為

                       QUERY PLAN
      --------------------------------------------
       Sort
         Sort Key: ((SubPlan 1)), t1.c1
         ->  Hash Join
               Hash Cond: (t1.c1 = t3.c1)
               ->  Seq Scan on t1
               ->  Hash
                     ->  Seq Scan on t3
               SubPlan 1
                 ->  Aggregate
                       ->  Seq Scan on t2
                             Filter: (c1 = t1.c1)
      (11 rows)
      

      由於相關子查詢出現在targetlist(查詢返回列表)裡,對於t1.c1=t2.c1不匹配的場景仍然需要輸出值,因此使用left-outerjoin關聯T1&T2確保t1.c1=t2.c1在不匹配時子SSQ能夠返回不匹配的補空值,但是這裡帶了count語句及時在t1.c1=t2.t1不匹配時需要輸出0,因此可以使用一個case-when NULL then 0 else count(*)來代替。

      上述SQL語句可以改寫為:

      with ssq as
      (
          select count(*) cnt, c1 from t2 group by c1
      )
      select case when
                  ssq.cnt is null then 0
                  else ssq.cnt
             end cnt, t1.c1, t3.c1
      from t1 left join ssq on ssq.c1 = t1.c1,t3
      where t1.c1 = t3.c1
      order by ssq.cnt, t1.c1;
      

      改寫後的執行計劃為

                      QUERY PLAN
      -------------------------------------------
       Sort
         Sort Key: ssq.cnt, t1.c1
         CTE ssq
           ->  HashAggregate
                 Group By Key: t2.c1
                 ->  Seq Scan on t2
         ->  Hash Join
               Hash Cond: (t1.c1 = t3.c1)
               ->  Hash Left Join
                     Hash Cond: (t1.c1 = ssq.c1)
                     ->  Seq Scan on t1
                     ->  Hash
                           ->  CTE Scan on ssq
               ->  Hash
                     ->  Seq Scan on t3
      (15 rows)
      
    • 相關條件為不等值場景

      例如:

      複製程式碼select t1.c1, t1.c2
      from t1
      where t1.c1 = (select agg() from t2.c2 > t1.c2);
      

      對於非等值相關條件的SubLink目前無法提升,從語義上可以通過做2次join(一次CorrelationKey,一次rownum自關聯)達到提升改寫的目的。

      改寫方案有兩種。

      • 子查詢改寫方式

        複製程式碼select t1.c1, t1.c2
        from t1, (
            select t1.rowid, agg() aggref
            from t1,t2
            where t1.c2 > t2.c2 group by t1.rowid
        ) dt /* derived table */
        where t1.rowid = dt.rowid AND t1.c1 = dt.aggref;
        
      • CTE改寫方式

        複製程式碼WITH dt as
        (
            select t1.rowid, agg() aggref
            from t1,t2
            where t1.c2 > t2.c2 group by t1.rowid
        )
        select t1.c1, t1.c2
        from t1, derived_table
        where t1.rowid = derived_table.rowid AND
        t1.c1 = derived_table.aggref;
        

     須知:
    - 對於AGG型別為count(*)時需要進行CASE-WHEN對沒有match的場景補0處理,非COUNT(*)場景NULL處理。
    - CTE改寫方式如果有sharescan支援效能上能夠更優。

更多優化示例

示例:修改select語句,將子查詢修改為和主表的join,或者修改為可以提升的subquery,但是在修改前後需要保證語義的正確性。

explain (costs off) select * from t1 where t1.c1 in (select t2.c1 from t2 where t1.c1 = t2.c2);
           QUERY PLAN
--------------------------------
 Seq Scan on t1
   Filter: (SubPlan 1)
   SubPlan 1
     ->  Seq Scan on t2
           Filter: (t1.c1 = c2)
(5 rows)

上面事例計劃中存在一個subPlan,為了消除這個subPlan可以修改語句為:

explain (costs off) select * from t1 where exists (select t2.c1 from t2 where t1.c1 = t2.c2 and t1.c1 = t2.c1);
                QUERY PLAN
------------------------------------------
 Hash Join
   Hash Cond: (t1.c1 = t2.c2)
   ->  Seq Scan on t1
   ->  Hash
         ->  HashAggregate
               Group By Key: t2.c2, t2.c1
               ->  Seq Scan on t2
                     Filter: (c2 = c1)
(8 rows)

從計劃可以看出,subPlan消除了,計劃變成了兩個表的hash join,這樣會大大提高執行效率。

統計資訊調優

統計資訊調優介紹

openGauss是基於代價估算生成的最優執行計劃。優化器需要根據analyze收集的統計資訊行數估算和代價估算,因此統計資訊對優化器行數估算和代價估算起著至關重要的作用。通過analyze收集全域性統計資訊,主要包括:pg_class表中的relpages和reltuples;pg_statistic表中的stadistinct、stanullfrac、stanumbersN、stavaluesN、histogram_bounds等。

例項分析1:未收集統計資訊導致查詢效能差

在很多場景下,由於查詢中涉及到的表或列沒有收集統計資訊,會對查詢效能有很大的影響。

表結構如下所示:

CREATE TABLE LINEITEM
(
L_ORDERKEY         BIGINT        NOT NULL
, L_PARTKEY        BIGINT        NOT NULL
, L_SUPPKEY        BIGINT        NOT NULL
, L_LINENUMBER     BIGINT        NOT NULL
, L_QUANTITY       DECIMAL(15,2) NOT NULL
, L_EXTENDEDPRICE  DECIMAL(15,2) NOT NULL
, L_DISCOUNT       DECIMAL(15,2) NOT NULL
, L_TAX            DECIMAL(15,2) NOT NULL
, L_RETURNFLAG     CHAR(1)       NOT NULL
, L_LINESTATUS     CHAR(1)       NOT NULL
, L_SHIPDATE       DATE          NOT NULL
, L_COMMITDATE     DATE          NOT NULL
, L_RECEIPTDATE    DATE          NOT NULL
, L_SHIPINSTRUCT   CHAR(25)      NOT NULL
, L_SHIPMODE       CHAR(10)      NOT NULL
, L_COMMENT        VARCHAR(44)   NOT NULL
) with (orientation = column, COMPRESSION = MIDDLE);

CREATE TABLE ORDERS
(
O_ORDERKEY        BIGINT        NOT NULL
, O_CUSTKEY       BIGINT        NOT NULL
, O_ORDERSTATUS   CHAR(1)       NOT NULL
, O_TOTALPRICE    DECIMAL(15,2) NOT NULL
, O_ORDERDATE     DATE NOT NULL
, O_ORDERPRIORITY CHAR(15)      NOT NULL
, O_CLERK         CHAR(15)      NOT NULL
, O_SHIPPRIORITY  BIGINT        NOT NULL
, O_COMMENT       VARCHAR(79)   NOT NULL
)with (orientation = column, COMPRESSION = MIDDLE);

查詢語句如下所示:

explain verbose select
count(*) as numwait 
from
lineitem l1,
orders 
where
o_orderkey = l1.l_orderkey
and o_orderstatus = 'F'
and l1.l_receiptdate > l1.l_commitdate
and not exists (
select
*
from
lineitem l3
where
l3.l_orderkey = l1.l_orderkey
and l3.l_suppkey <> l1.l_suppkey
and l3.l_receiptdate > l3.l_commitdate
)
order by
numwait desc;

當出現該問題時,可以通過如下方法確認查詢中涉及到的表或列有沒有做過analyze收集統計資訊。

  1. 通過explain verbose執行query分析執行計劃時會提示WARNING資訊,如下所示:

    WARNING:Statistics in some tables or columns(public.lineitem.l_receiptdate, public.lineitem.l_commitdate, public.lineitem.l_orderkey, public.lineitem.l_suppkey, public.orders.o_orderstatus, public.orders.o_orderkey) are not collected.
    HINT:Do analyze for them in order to generate optimized plan.
    
  2. 可以通過在pg_log目錄下的日誌檔案中查詢以下資訊來確認是當前執行的query是否由於沒有收集統計資訊導致查詢效能變差。

    2017-06-14 17:28:30.336 CST 140644024579856 20971684 [BACKEND] LOG:Statistics in some tables or columns(public.lineitem.l_receiptdate, public.lineitem.l_commitdate, public.lineitem.l_orderkey, public.linei
    tem.l_suppkey, public.orders.o_orderstatus, public.orders.o_orderkey) are not collected.
    2017-06-14 17:28:30.336 CST 140644024579856 20971684 [BACKEND] HINT:Do analyze for them in order to generate optimized plan.
    

當通過以上方法檢視到哪些表或列沒有做analyze,可以通過對WARNING或日誌中上報的表或列做analyze可以解決由於為收集統計資訊導致查詢變慢的問題。

運算元級調優

運算元級調優介紹

一個查詢語句要經過多個運算元步驟才會輸出最終的結果。由於個別運算元耗時過長導致整體查詢效能下降的情況比較常見。這些運算元是整個查詢的瓶頸運算元。通用的優化手段是EXPLAIN ANALYZE/PERFORMANCE命令檢視執行過程的瓶頸運算元,然後進行鍼對性優化。

如下面的執行過程資訊中,Hashagg運算元的執行時間佔總時間的:(51016-13535)/ 56476 ≈66%,此處Hashagg運算元就是這個查詢的瓶頸運算元,在進行效能優化時應當優先考慮此運算元的優化。

運算元級調優示例

示例1:基表掃描時,對於點查或者範圍掃描等過濾大量資料的查詢,如果使用SeqScan全表掃描會比較耗時,可以在條件列上建立索引選擇IndexScan進行索引掃描提升掃描效率。

openGauss=#  explain (analyze on, costs off) select * from store_sales where ss_sold_date_sk = 2450944;
 id |             operation          |       A-time        | A-rows | Peak Memory  | A-width
----+--------------------------------+---------------------+--------+--------------+---------
  1 | ->  Streaming (type: GATHER)   | 3666.020            |   3360 | 195KB        |
  2 |    ->  Seq Scan on store_sales | [3594.611,3594.611] |   3360 | [34KB, 34KB] |
(2 rows)

 Predicate Information (identified by plan id) 
-----------------------------------------------
   2 --Seq Scan on store_sales
         Filter: (ss_sold_date_sk = 2450944)
         Rows Removed by Filter: 4968936
openGauss=#  create index idx on store_sales_row(ss_sold_date_sk);
CREATE INDEX
openGauss=#  explain (analyze on, costs off) select * from store_sales_row where ss_sold_date_sk = 2450944;
 id |                   operation                    |     A-time      | A-rows | Peak Memory  | A-width
----+------------------------------------------------+-----------------+--------+--------------+----------
  1 | ->  Streaming (type: GATHER)                   | 81.524          |   3360 | 195KB        |
  2 |    ->  Index Scan using idx on store_sales_row | [13.352,13.352] |   3360 | [34KB, 34KB] |
(2 rows)

上述例子中,全表掃描返回3360條資料,過濾掉大量資料,在ss_sold_date_sk列上建立索引後,使用IndexScan掃描效率顯著提高,從3.6秒提升到13毫秒。

示例2:如果從執行計劃中看,兩表join選擇了NestLoop,而實際行數比較大時,NestLoop Join可能執行比較慢。如下的例子中NestLoop耗時181秒,如果設定引數enable_mergejoin=off關掉Merge Join,同時設定引數enable_nestloop=off關掉NestLoop,讓優化器選擇HashJoin,則Join耗時提升至200多毫秒。

示例3:通常情況下Agg選擇HashAgg效能較好,如果大結果集選擇了Sort+GroupAgg,則需要設定enable_sort=off,HashAgg耗時明顯優於Sort+GroupAgg。

經驗總結:SQL語句改寫規則

根據資料庫的SQL執行機制以及大量的實踐,總結髮現:通過一定的規則調整SQL語句,在保證結果正確的基礎上,能夠提高SQL執行效率。如果遵守這些規則,常常能夠大幅度提升業務查詢效率。

  • 使用union all代替union。

    union在合併兩個集合時會執行去重操作,而union all則直接將兩個結果集合並、不執行去重。執行去重會消耗大量的時間,因此,在一些實際應用場景中,如果通過業務邏輯已確認兩個集合不存在重疊,可用union all替代union以便提升效能。

  • join列增加非空過濾條件。

    若join列上的NULL值較多,則可以加上is not null過濾條件,以實現資料的提前過濾,提高join效率。

  • not in轉not exists。

    not in語句需要使用nestloop anti join來實現,而not exists則可以通過hash anti join來實現。在join列不存在null值的情況下,not exists和not in等價。因此在確保沒有null值時,可以通過將not in轉換為not exists,通過生成hash join來提升查詢效率。

    如下所示,如果t2.d2欄位中沒有null值(t2.d2欄位在表定義中not null)查詢可以修改為:

    SELECT * FROM t1 WHERE  NOT EXISTS (SELECT * FROM t2 WHERE t1.c1=t2.d2);
    

    產生的計劃如下:

    QUERY PLAN
    ------------------------------
    Hash Anti Join
    Hash Cond: (t1.c1 = t2.d2)
    ->  Seq Scan on t1
    ->  Hash
    ->  Seq Scan on t2
    (5 rows)
    
  • 選擇hashagg。

    查詢中GROUP BY語句如果生成了groupagg+sort的plan效能會比較差,可以通過加大work_mem的方法生成hashagg的plan,因為不用排序而提高效能。

  • 嘗試將函式替換為case語句。

    openGauss函式呼叫效能較低,如果出現過多的函式呼叫導致效能下降很多,可以根據情況把可下推函式的函式改成CASE表示式。

  • 避免對索引使用函式或表示式運算。

    對索引使用函式或表示式運算會停止使用索引轉而執行全表掃描。

  • 儘量避免在where子句中使用!=或<>操作符、null值判斷、or連線、引數隱式轉換。

  • 對複雜SQL語句進行拆分。

    對於過於複雜並且不易通過以上方法調整效能的SQL可以考慮拆分的方法,把SQL中某一部分拆分成獨立的SQL並把執行結果存入臨時表,拆分常見的場景包括但不限於:

    • 作業中多個SQL有同樣的子查詢,並且子查詢資料量較大。
    • Plan cost計算不準,導致子查詢hash bucket太小,比如實際資料1000W行,hash bucket只有1000。
    • 函式(如substr、to_number)導致大資料量子查詢選擇度計算不準。

 

SQL調優關鍵引數調整

本節將介紹影響openGauss SQL調優效能的關鍵資料庫主節點配置引數,配置方法參見配置執行引數

表 1 資料庫主節點配置引數

引數/參考值

描述

enable_nestloop=on

控制查詢優化器對巢狀迴圈連線(Nest Loop Join)型別的使用。當設定為“on”後,優化器優先使用Nest Loop Join;當設定為“off”後,優化器在存在其他方法時將優先選擇其他方法。

說明:

如果只需要在當前資料庫連線(即當前Session)中臨時更改該引數值,則只需要在SQL語句中執行如下命令:

複製程式碼SET enable_nestloop to off;

此引數預設設定為“on”,但實際調優中應根據情況選擇是否關閉。一般情況下,在三種join方式(Nested Loop、Merge Join和Hash Join)裡,Nested Loop效能較差,實際調優中可以選擇關閉。

enable_bitmapscan=on

控制查詢優化器對點陣圖掃描規劃型別的使用。設定為“on”,表示使用;設定為“off”,表示不使用。

說明:

如果只需要在當前資料庫連線(即當前Session)中臨時更改該引數值,則只需要在SQL語句中執行命令如下命令:

複製程式碼SET enable_bitmapscan to off;

bitmapscan掃描方式適用於“where a > 1 and b > 1”且a列和b列都有索引這種查詢條件,但有時其效能不如indexscan。因此,現場調優如發現查詢效能較差且計劃中有bitmapscan運算元,可以關閉bitmapscan,看效能是否有提升。

enable_hashagg=on

控制優化器對Hash聚集規劃型別的使用。

enable_hashjoin=on

控制優化器對Hash連線規劃型別的使用。

enable_mergejoin=on

控制優化器對融合連線規劃型別的使用。

enable_indexscan=on

控制優化器對索引掃描規劃型別的使用。

enable_indexonlyscan=on

控制優化器對僅索引掃描規劃型別的使用。

enable_seqscan=on

控制優化器對順序掃描規劃型別的使用。完全消除順序掃描是不可能的,但是關閉這個變數會讓優化器在存在其他方法的時候優先選擇其他方法。

enable_sort=on

控制優化器使用的排序步驟。該設定不可能完全消除明確的排序,但是關閉這個變數可以讓優化器在存在其他方法的時候優先選擇其他方法。

rewrite_rule

控制優化器是否啟用LAZY_AGG和MAGIC_SET重寫規則。

sql_beta_feature

控制優化器是否啟用。SEL_SEMI_POISSON/SEL_EXPR_INSTR/PARAM_PATH_GEN/RAND_COST_OPT/PARAM_PATH_OPT/PAGE_EST_OPT/CANONICAL_PATHKEY/PARTITION_OPFUSION測試功能。