本文為您介紹常見的SQL問題以及優化示例。
并行度優化
并行度是衡量并行計算程度的一個指標,從執行計劃上來看,例如ID為M1的任務,使用1000個Instance來執行,我們就說M1的并行度是1000
。合理地設置并調整任務并行度,可以使任務執行效率更高。
并行度優化場景如下。
強制一個Instance執行
某些操作強制一個Instance執行任務,例如:
做聚合的時候,沒有進行
group by
或者group by
一個常量。窗口函數的
over
語句里指定partition by
一個常量。SQL中指定
distribute by
、cluster by
一個常量。
解決方案:針對此情形,建議您檢查這些操作是否必要,能否去掉,盡量取消類似操作,避免強制一個Instance執行任務。
Instance數量過多或過少
調整并行度不一定是越多越好,Instance數量過多會從如下兩個方面影響執行速度:
Instance越多,等待資源的時間越長,排隊次數也更多。
每個Instance初始化需要時間,并行度越高,初始化占用的總時間就越長,有效的執行時間占比就越低。
以下情形會導致使用很多Instance:
需要讀取很多小分區的數據,例如一個數據查詢SQL語句讀取10000個分區,那么系統會強制使用10000個Instance。
解決方案:您需要設計SQL,減少分區的數量,可以從進行分區裁剪、篩除不需要讀的分區、將大作業拆分成小作業方面進行考慮。
每次讀取
256 MB
數據太少,導致Instance的執行時間太短,而由于輸入數據很大,反而導致了并行度過大,使Instance大多數時間在排隊等資源。解決方案:使用如下命令調大單個并發處理的數據大小,從而減少Instance數量。
SET odps.stage.mapper.split.size=<256>; SET odps.stage.reducer.num=<并發數>;
Instance數量設置方法
讀表的Task
方法1:通過設置參數調整并行度。
-- 設定一個map的最大數據輸入量,單位MB -- 默認256,區間[1,Integer.MAX_VALUE] SET odps.sql.mapper.split.size=<value>;
方法2:MaxCompute提供split size hint方式,可以針對單個讀表操作來調整并行度。
--設置split size大小為1MB,此hint會在讀src表時,按照1MB的大小來切分task SELECT a.key FROM src a /*+split_size(1)*/ JOIN src2 b ON a.key=b.key;
方法3:在表級別按照大小、行數或指定并行度進行切分。
由于方法1中
odps.sql.mapper.split.size
只支持Mapper Stage的整體設置,且最低為1 MB,必要時,您可以根據表的維度調整并行度,尤其是在表中每行數據的size較小,而后續計算負擔較重的情況下,可以減少并行處理的行數,從而提高任務的并行度。調整并行度的方式如下:
設置表級別單個并行處理的分片數據大小。
SET odps.sql.split.size = {"table1": 1024, "table2": 512};
設置表級別單個并行處理的行數。
SET odps.sql.split.row.count = {"table1": 100, "table2": 500};
設置表級別的并行度。
SET odps.sql.split.dop = {"table1": 1, "table2": 5};
說明odps.sql.split.row.count
和odps.sql.split.dop
只能用于內部表、非事務表和非聚簇表。非讀表的Task
主要有如下三種方式調整并行度:
方法1:調整
odps.stage.reducer.num
值。使用如下命令強制設置Reducer并行度,該設置將影響所有相關的Task。-- 設定Reduce Task的instance數量 -- 可調整區間為[1,99999] SET odps.stage.reducer.num=<value>;
方法2:調整
odps.stage.joiner.num
值。使用如下命令強制設置Reducer并行度,會影響所有相關的Task。-- 設定Joiner Task的instance數量 -- 可調整區間為[1,99999] SET odps.stage.joiner.num=<value>;
方法3:調整
odps.sql.mapper.split.size
值。非讀表Task的并行度會受到輸入Task的并行度影響,通過調整讀表Task的并行度間接調整非讀表Task的并行度。
窗口函數優化
如果SQL語句中使用了窗口函數,通常每個窗口函數會形成一個Reduce作業。如果窗口函數較多,會消耗過多的資源。您可以對符合下述條件的窗口函數進行優化:
窗口函數在OVER關鍵字后面要完全相同,要有相同的分組和排序條件。
多個窗口函數在同一層SQL中執行。
符合上述2個條件的窗口函數會合并為一個Reduce執行。SQL示例如下所示。
SELECT
RANK() OVER (PARTITION BY A ORDER BY B desc) AS RANK,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY B desc) AS row_num
FROM MyTable;
子查詢優化
子查詢如下所示。
SELECT * FROM table_a a WHERE a.col1 IN (SELECT col1 FROM table_b b WHERE xxx);
當此語句中的table_b子查詢返回的col1的個數超過9999個時,系統會報錯為records returned from subquery exceeded limit of 9999
。此時您可以使用Join語句來代替,如下所示。
SELECT a.* FROM table_a a JOIN (SELECT DISTINCT col1 FROM table_b b WHERE xxx) c ON (a.col1 = c.col1);
如果沒有使用DISTINCT關鍵字,而子查詢表c返回的結果中有相同的col1的值,可能會導致a表的結果數變多。
DISTINCT關鍵字會導致查詢在同一個Worker中執行。如果子查詢數據量較大,會導致查詢比較慢。
如果業務上已經確保子查詢中col1列值無重復,您可以刪除DISTINCT關鍵字,以提高性能。
Join語句優化
當兩個表進行Join操作時,建議在如下位置使用WHERE子句:
主表的分區限制條件可以寫在WHERE子句中(建議先用子查詢過濾)。
主表的WHERE子句建議寫在SQL語句最后。
從表分區限制條件不要寫在WHERE子句中,建議寫在ON條件或者子查詢中。
示例如下。
SELECT * FROM A JOIN (SELECT * FROM B WHERE dt=20150301)B ON B.id=A.id WHERE A.dt=20150301;
SELECT * FROM A JOIN B ON B.id=A.id WHERE B.dt=20150301; --不建議使用。此語句會先執行Join操作后進行分區裁剪,導致數據量變大,性能下降。
SELECT * FROM (SELECT * FROM A WHERE dt=20150301)A JOIN (SELECT * FROM B WHERE dt=20150301)B ON B.id=A.id;
聚合函數優化
使用wm_concat
函數替代collect_list
函數,實現聚合函數的優化,使用示例如下。
-- collect_list實現
SELECT concat_ws(',', sort_array(collect_list(key))) FROM src;
-- wm_concat實現更優
SELECT wm_concat(',', key) WITHIN GROUP (ORDER BY key) FROM src;
-- collect_list實現
SELECT array_join(collect_list(key), ',') FROM src;
-- wm_concat實現更優
SELECT wm_concat(',', key) FROM src;