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自身提供的聚合函式和視窗函式去完成一些複雜的分析