1. 程式人生 > >Greenplum優化--SQL調優篇

Greenplum優化--SQL調優篇

目錄

資料庫查詢預準備

1. VACUUM

  • vacuum只是簡單的回收空間且令其可以再次使用,沒有請求排它鎖,仍舊可以對錶讀寫
  • vacuum full執行更廣泛的處理,包括跨塊移動行,以便把表壓縮至使用最少的磁碟塊數目儲存。相對vacuum要慢,而且會請求排它鎖。
  • 定期執行:在日常維護中,需要對資料字典定期執行vacuum,可以每天在資料庫空閒的時候進行。然後每隔一段較長時間(兩三個月)對系統表執行一次vacuum full,這個操作需要停機,比較耗時,大表可能耗時幾個小時。
  • reindex:執行vacuum之後,最好對錶上的索引進行重建

2. ANALYZE

  • 命令:analyze [talbe [(column,..)]]
  • 收集表內容的統計資訊,以優化執行計劃。如建立索引後,執行此命令,對於隨即查詢將會利用索引。
  • 自動統計資訊收集
  • 在postgresql.conf中有控制自動收集的引數gp_autostats_mode設定,gp_autostats_mode三個值:none、no_change、on_no_stats(預設)
    • none:禁止收集統計資訊
    • on change:當一條DML執行後影響的行數超過gp_autostats_on_change_threshold引數指定的值時,會執行完這條DML後再自動執行一個analyze 的操作來收集表的統計資訊。
    • no_no_stats:當使用create talbe as select 、insert 、copy時,如果在目標表中沒有收集過統計資訊,那麼會自動執行analyze 來收集這張表的資訊。gp預設使用on_no_stats,對資料庫的消耗比較小,但是對於不斷變更的表,資料庫在第一次收集統計資訊之後就不會再收集了。需要人為定時執行analyze.
  • 如果有大量的執行時間在1分鐘以下的SQL,你會發現大量的時間消耗在收集統計資訊上。為了降低這一部分的消耗,可以指定對某些列不收集統計資訊,如下所示:

    1. create table test(id int, name text,note text);
    

    上面是已知道表列note不需出現在join列上,也不會出現在where語句的過濾條件下,因為可以把這個列設定為不收集統計資訊:

    1. alter table test alter note SET STATISTICS 0;
    

3. EXPLAIN執行計劃

顯示規劃器為所提供的語句生成的執行規劃。

  • cost:返回第一行記錄前的啟動時間, 和返回所有記錄的總時間(以磁碟頁面存取為
    單位計量)
  • rows:根據統計資訊估計SQL返回結果集的行數
  • width:返回的結果集的每一行的長度,這個長度值是根據pg_statistic表中的統計資訊
    來計算的。

4. 兩種聚合方式

  • hashaggregate
    根據group by欄位後面的值算出hash值,並根據前面使用的聚合函式在記憶體中維護對應的列表,幾個聚合函式就有幾個陣列。相同資料量的情況下,聚合欄位的重複度越小,使用的記憶體越大。
  • groupaggregate
    先將表中的資料按照group by的欄位排序,在對排好序的資料進行全掃描,並進行聚合函式計算。消耗記憶體基本是恆定的。
  • 選擇
    在SQL中有大量的聚合函式,group by的欄位重複值比較少的時候,應該用groupaggregate

5. 關聯

分為三類:hash join、nestloop join、merge join,在保證sql執行正確的前提下,規劃器優先採用hash join。

  • hash join: 先對其中一張關聯的表計算hash值,在記憶體中用一個散列表儲存,然後對另外一張表進行全表掃描,之後將每一行與這個散列表進行關聯。
  • nestedloop:關聯的兩張表中的資料量比較小的表進行廣播,如笛卡爾積:select * fromtest1,test2
  • merge join:將兩張表按照關聯鍵進行排序,然後按照歸併排序的方式將資料進行關聯,效率比hash join差。full outer join只能採用merge join來實現。
  • 關聯的廣播與重分佈解析P133,一般規劃器會自動選擇最優執行計劃。
  • 有時會導致重分佈和廣播,比較耗時的操作

6. 重分佈

一些sql查詢中,需要資料在各節點重新分佈,受制於網路傳輸、磁碟I/O,重分佈的速度比較慢。

  • 關聯鍵強制型別轉換
    一般,表按照指定的分佈鍵作hash分部。如果兩個表按照id:intege、id:numericr分佈,關聯時,需要有一個表id作強制型別轉化,因為不同型別的hash值不一樣,因而導致資料重分佈。
  • 關聯鍵與分部鍵不一致
  • group by、開窗函式、grouping sets會引發重分佈

查詢優化

通過explain觀察執行計劃,從而確定如果優化SQL。

1. explain引數

顯示規劃器為所提供的語句生成的執行規劃。

  • cost:返回第一行記錄前的啟動時間, 和返回所有記錄的總時間(以磁碟頁面存取為單位計量)
  • rows:根據統計資訊估計SQL返回結果集的行數
  • width:返回的結果集的每一行的長度,這個長度值是根據pg_statistic表中的統計資訊來計算的。

2. 選擇合適分佈鍵

分佈鍵選擇不當會導致重分佈、資料分佈不均等,而資料分佈不均會使SQL集中在一個segment節點的執行,限制了gp整體的速度。

  • 使所有節點資料存放是均勻的,資料分佈均勻才能充分利用多臺機器查詢,發揮分散式的優勢。
  • join、開窗函式等儘量以分佈鍵作為關聯鍵、分割槽鍵。尤其需要注意的是join、開窗函式會依據關聯鍵、分割槽鍵做重分佈或者廣播操作,因而若分佈鍵和關聯鍵不一致,不論如何修改分佈鍵,也是需要再次重分佈的。
  • 儘量保證where條件產生的結果集的儲存也儘量是均勻的。
  • 檢視某表是否分佈不均: select gp_segment_id,count(*) from fact_tablegroup by gp_segment_id
  • 在segment一級,可以通過select gp_segment_id,count(*) from fact_table group by gp_segment_id的方式檢查每張表的資料是否均勻存放
  • 在系統級,可以直接用df -h 或du -h檢查磁碟或者目錄資料是否均勻
  • 檢視資料庫中資料傾斜的表
    首先定義資料傾斜率為:最大子節點資料量/平均節點資料量。為避免整張表的資料量為空,同時對結果的影響很小,在平均節點資料量基礎上加上一個很小的值,SQL如下:
SELECT tabname,
max(SIZE)/(avg(SIZE)+0.001) AS max_div_avg,
sum(SIZE) total_size
FROM
(SELECT gp_segment_id,
oid::regclass tabname,
pg_relation_size(oid) SIZE
FROM gp_dist_random('pg_class')
WHERE relkind='r'
AND relstorage IN ('a','h')) t
GROUP BY tabname
ORDER BY 2 DESC;

3. 分割槽表

按照某欄位進行分割槽,不影響資料在資料節點上的分佈,但是,僅在單個數據節點上,對資料進行分割槽儲存。可以加快分割槽欄位的查詢速度。

4. 壓縮表

對於大AO表和分割槽表使用壓縮,以節省儲存空間並提高系統I/O,也可以在欄位級別配置壓縮。應用場景:

  • 不需要對錶進行更新和刪除操作
  • 訪問表的時候基本上是全表掃描,不需要建立索引
  • 不能經常對錶新增欄位或者修改欄位型別

5. 分組擴充套件

Greenplum資料庫的GROUP BY擴充套件可以執行某些常用的計算,且比應用程式或者儲存過程效率高。

    GROUP BY ROLLUP(col1, col2, col3)
    GROUP BY CUBE(col1, col2, col3)
    GROUP BY GROUPING SETS((col1, col2), (col1, col3))

ROLLUP 對分組欄位(或者表示式)從最詳細級別到最頂級別計算聚合計數。ROLLUP的引數是一個有序分組欄位列表,它計算從右向左各個級別的聚合。例如 ROLLUP(c1, c2, c3) 會為下列分組條件計算聚集:

    (c1, c2, c3)
    (c1, c2)
    (c1)
    ()

CUBE 為分組欄位的所有組合計算聚合。例如 CUBE(c1, c2, c3) 會計算一下聚合:

    (c1, c2, c3)
    (c1, c2)
    (c2, c3)
    (c1, c3)
    (c1)
    (c2)
    (c3)
    ()

GROUPING SETS 指定對那些欄位計算聚合,它可以比ROLLUP和CUBE更精確地控制分割槽條件。

6. 視窗函式

視窗函式可以實現在結果集的分組子集上的聚合或者排名函式,例如 sum(population) over (partition by city)。視窗函式功能強大,效能優異。因為它在資料庫內部進行計算,避免了資料傳輸。

  • 視窗函式row_number()計算一行在分組子集中的行號,例如 row_number() over (order by id)。
  • 如果查詢計劃顯示某個表被掃描多次,那麼通過視窗函式可能可以降低掃描次數。
  • 視窗函式通常可以避免使用自關聯。

7. 列儲存和行儲存

列儲存亦即同一列的資料都連續儲存在一個物理檔案中,有更高的壓縮率,適合在款表中對部分欄位進行篩選的場景。
需要注意的是:若叢集中節點較多,而且表的列也較多,每個節點的每一列將會至少產生一個檔案,那麼總體上將會產生比較多的檔案,對錶的DDL操作就會比較慢。在和分割槽表使用時,將會產生更多檔案,甚至可能超過linux的檔案控制代碼限制,要尤其注意。

  • 行儲存:如果記錄需要update/delete,那麼只能選擇非壓縮的行存方式。對於查詢,如果選擇的列的數量經常超過30個以上的列,那麼也應該選擇行存方式。
  • 列儲存:如果選擇列的數量非常有限,並且希望通過較高的壓縮比換取海量資料查詢時的較好的IO效能,那麼就應該選擇列存模式。其中,列存分割槽表,每個分割槽的每個列都會有一個對應的物理檔案,所以要注意避免檔案過多,導致可能超越linux上允許同時開啟檔案數量的上限以及DDL命令的效率很差。

8. 函式和儲存過程

雖然支援遊標但是,儘量不要使用遊標方式處理資料,而是應該把資料作為一個整體進行操作。

9. 索引使用

  • 如果是從超大結果集合中返回非常小的結果集(不超過5%),建議使用BTREE索引(非典型資料倉庫操作)
  • 表記錄的儲存順序最好與索引一致,可以進一步減少IO(好的index cluster)
  • where條件中的列用or的方式進行join,可以考慮使用索引
  • 鍵值大量重複時,比較適合使用bitmap索引

10. NOT IN

  • 在gp4.3中已經進行了優化,採用hash left anti semi join進行連線。
  • 以下只針對gp4.1及之前

    • 有not in的SQL,都會採用笛卡爾積來執行,採用nested join,效率極差
    • not in==》改用left join去重後的表關聯來實現
    • 例子

       select * from test1 where col1 not in (select col2 from test1)
      

      改為

       select * from test1 a left join (select col2 from test1 group bycol2) b on a.col1=b.col2 where b.col2 is null
      

      執行時間由30多秒提升至92毫秒。

11. 聚合函式太多

  • 一條SQL中聚合函式太多,而且可能由於統計資訊不夠詳細或者SQL太負責,錯選hashaggregate來執行,導致記憶體不足。
  • 解決方法:
    • 拆分成多個SQL來執行,減少hashaggregate使用的記憶體
    • 執行enable_hashagg=off,把hashaggregate引數關掉,強制不採用。將會採用groupaggregate,這樣排序時間會長一些,但是記憶體可控,建議採用這種方式比較簡單。

12. 資源佇列

資料寫入、查詢分別使用不同的使用者,GP建立使用者時為不同使用者指定不同的資源佇列。

13. 其它優化技巧

  • 用group by對distinct改寫,因為DISTINCT要進行排序操作
  • 用UNION ALL加GROUP BY的方式對UNION改寫
  • 儘量使用GREENPLUM自身提供的聚合函式和視窗函式去完成一些複雜的分析

參考