轉載:http://www.voidcn.com/article/p-kmaltben-bse.html
查詢速度慢, 如何優化?
解決方法1: 避免單節點處理
雖然Presto是分布式查詢引擎, 但是一些操作是必須在單節點中處理的. 例如:
-
count(distinct x)
- 考慮使用approx_distinct(x)代替
- 但是需要注意這個函數有個大約在2.3%的標准誤差, 如果需要精確統計的情況, 請繞道.
-
UNION
UNION有個功能是: 如果兩條記錄一樣, 會只保留一條記錄(去重).
- 如果不考慮去重的情況, 請使用UNION ALL
-
ORDER BY
Presto對數據排序是作用在單節點上的
- 如果要排序的數據量超過百萬行, 要謹慎考慮. 如果非要排序,盡量將排序的字段減少些.
解決方法2: 減少表掃描的范圍
通過添加條件達到減少表掃描的范圍.
也可以考慮將大數據量的表, 水平查分, 通過查不同的表分區達到效果.
解決方法3: 避免使用 SELECT * FROM
要明確寫出所有要訪問的列, 能加快速度.比如:你去拿幾個東西過來/你去把1,2,3拿過來一個道理
例如
SELECT * FROM my_table
改成:
SELECT id, name, address FROM my_table
解決方法4: 將幾個LIKE語句放到函數regexp_like()
Presto的查詢優化器不能改善許多LIKE語句使用的地方, 導致這樣的語句查詢速度慢.
例如
SELECT ... FROM access WHERE method LIKE '%GET%' OR method LIKE '%POST%' OR method LIKE '%PUT%' OR method LIKE '%DELETE%'
上面的語句能用regexp_like函數優化成一句
SELECT ... FROM access WHERE regexp_like(method, 'GET|POST|PUT|DELETE')
如何優化JOIN性能?
盡量讓JOIN的條件簡單,最好是ON后面的比較表達式兩邊必涉及計算。
例如
SELECT a.date, b.name FROM left_table a JOIN right_table b ON a.date = CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR)
上面的SQL語句的JOIN性能不高,因為JION條件包含了表達式計算。我們可以通過子查詢的形式來優化上面的語句。
SELECT a.date, b.name FROM left_table a JOIN ( SELECT CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR) date, # generate join key name FROM right_table ) b ON a.date = b.date # Simple equi-join
上面的語句,就是直接比較兩個VARCHAR的值,這樣會比比較一個VARCHAR和一個表達式結果的性能高。
我們還能繼續優化,使用Presto的WITH語句進行子查詢。
WITH b AS (
SELECT CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR) date, # generate join key name FROM right_table ) SELECT a.date, b.name FROM left_table a JOIN b ON a.date = b.date
如何使查詢簡單化
解決方法1: 使用WITH語句
如果你的查詢語句非常復雜或者有多層嵌套的子查詢,請試着用WITH語句將子查詢分離出來。
例如
SELECT a, b, c FROM ( SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a ) tbl_alias
可以被重寫為下面的形式
WITH tbl_alias AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a) SELECT a, b, c FROM tbl_alias
同樣,也可以將各個步驟的子查詢通過WITH語句羅列出來,子查詢之間用“,”分割。
WITH tbl1 AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a), tbl2 AS (SELECT a, AVG(d) AS d FROM another_tbl GROUP BY a) SELECT tbl1.*, tbl2.* FROM tbl1 JOIN tbl2 ON tbl1.a = tbl2.a
解決方法2:在CREATE TABLE語句中使用WITH語句
如果CREATE TABLE語句的查詢部分很復雜或者潛逃了多層子查詢,就需要考慮用WITH語句
例如:
CREATE TABLE tbl_new AS WITH tbl_alias AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl1) SELECT a, b, c FROM tbl_alias
CREATE TABLE tbl_new AS WITH tbl_alias1 AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl1), tbl_alias2 AS (SELECT a, AVG(d) AS d FROM tbl2) SELECT tbl_alias1.*, tbl2_alias.* FROM tbl_alias1 JOIN tbl_alias2 ON tbl_alias1.a = tbl_alias2.a
解決方法3:用GROUP BY語句時,GROUP BY的目標可用數字代替
在Presto SQL中,GROUP BY語句需要與SELECT語句中的表達式保持一致,不然會提示語法錯誤。
例如:
SELECT TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT') hour, count(*) cnt FROM my_table GROUP BY TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT')
上面的SQL語句的GROUP BY部分可以用GROUP BY 1,2,3 ...來表示
SELECT TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT') hour, count(*) cnt FROM my_table GROUP BY 1
Note: 這些數字是從1開始的,有別於程序要思維從0開始。
Exceeded max (local) memory 錯誤
Presto會跟蹤每個查詢的內存使用情況.可用內存的多少是根據你的查詢計划變動的,所以在大多數情況下可以從寫查詢語句來達到優化內存使用的目的.
下面列出來的就是內存密集型的語句塊:
- district
- UNION
- ORDER BY
- GROUP BY (許多字段的情況)
- joins (各種JOIN)
解決方法1: 盡量少使用distinct
distinct 會排除所有不唯一的行.下面的例子就是檢查你的數據表中是否包含了相同的數據行(c1,c2,c3)
SELECT distinct c1, c2, c3 FROM my_table
上面的操作會存儲一整字段c1,c2和c3到presto的單個工作節點的內存, 然后檢查(c1,c2,c3)的唯一性. 隨着字段的增多以及字段數據量的增大,所需要的內存也會直線上升.
所以, 去掉查詢語句中的distinct關鍵字, 或者只在子查詢(有有限少量字段的情況下)使用.
解決方法2: 用approx_distinct(x)代替count(distinct x)
NOTE: approx_distinct(x)會返回一個正確的近似值, 如果只是需要看一個大概的趨勢,可以考慮.
解決方法3: 盡量用UNION ALL代替UNION
和distinct的原因類似, UNION有去重的功能, 所以會引發內存使用的問題.如果你只是拼接兩個或者多個SQL查詢的結果, 考慮用UNION ALL
解決方法4: 盡量避免ORDER BY
SELECT c1, c2 FROM my_table ORDER BY c1
Presto在排序的時候啟用的是單一節點進行工作, 所以整個數據需要在單節點內存限制的范圍內, 超過這個內存限制就會報錯.
如果你需要排序的數據在一個小的量級, 用ORDER BY沒有問題; 如果需要排序的數據在GB的級別,需要考慮其他的解決方案.
例如: 大量級的數據排序可以考慮結合HIVE和presto. 首先, 用Presto將大量的數據存儲到一個臨時表中,然后用HIVE取對數據排序.
解決方法5: 減少GROUP BY的字段
SELECT avg(c1), min_by(c2, time), max(c3), count(c4), ... FROM my_table GROUP BY c1, c2, c3, c4, ...
減少GROUP BY語句后面的排序一句字段的數量能減少內存的使用.
解決方法6:用大表取JOIN小表
下面這種用小數據表去JOIN大數據表的查詢會極度消耗內存.
SELECT * FROM small_table, large_table WHERE small_table.id = large_table.id
Presto 會默認執行廣播式的JOIN操作,它會將左表拆分到幾個工作節點上, 然后發送整個右表分別到已拆分好的處理左表的工作節點上. 如果右表非常大就會超出工作節點的內存限制,進而出錯.
所以需要用大表JOIN小表
SELECT * FROM large_table, small_table WHERE large_table.id = small_table.id
如果左表和右表都比較大怎么辦?
- 修改配置distributed-joins-enabled (presto version >=0.196)
- 在每次查詢開始使用distributed_join的session選項
-- set session distributed_join = 'true'
SELECT * FROM large_table, large_table1 WHERE large_table1.id = large_table.id
核心點就是使用distributed join. Presto的這種配置類型會將左表和右表同時以join key的hash value為分區字段進行分區. 所以即使右表也是大表,也會被拆分.缺點是會增加很多網絡數據傳輸, 所以會比broadcast join的效率慢.
查詢生成的大量數據優化的問題
Presto用JOSN text的形式保存數據。如果查詢出來的數據大於100G,Presto將傳輸大於100G的JSON text來保存查詢結果。所以,即使查詢處理即將完成,輸出這么大的JOSN text也會消耗很長時間。
解決方法1:不要用==SELECT *==
解決方法2:用result_output_redirect='true' 注釋
在查詢語句前添加注釋(result_output_redirect='true'),能讓查詢更快些。
-- set session result_output_redirect='true' select a, b, c, d FROM my_table
上面的語句能讓Presto用並行的方式生成查詢結果,能跳過在Presto協調器進行JSON轉換的過程。
Note: 但是,如果使用了ORDER BY語句,這個魔術注釋將被忽略。
Presto查詢優化
數據存儲
合理設置分區
與Hive類似,Presto會根據元信息讀取分區數據,合理的分區能減少Presto數據讀取量,提升查詢性能。
使用列式存儲
Presto對ORC文件讀取做了特定優化,因此在Hive中創建Presto使用的表時,建議采用ORC格式存儲。相對於Parquet,Presto對ORC支持更好。
使用壓縮
數據壓縮可以減少節點間數據傳輸對IO帶寬壓力,對於即席查詢需要快速解壓,建議采用snappy壓縮
預先排序
對於已經排序的數據,在查詢的數據過濾階段,ORC格式支持跳過讀取不必要的數據。比如對於經常需要過濾的字段可以預先排序。
SQL優化
- 只選擇使用必要的字段: 由於采用列式存儲,選擇需要的字段可加快字段的讀取、減少數據量。避免采用*讀取所有字段
- 過濾條件必須加上分區字段
- Group By語句優化: 合理安排Group by語句中字段順序對性能有一定提升。將Group By語句中字段按照每個字段distinct數據多少進行降序排列, 減少GROUP BY語句后面的排序一句字段的數量能減少內存的使用.
- Order by時使用Limit, 盡量避免ORDER BY: Order by需要掃描數據到單個worker節點進行排序,導致單個worker需要大量內存
- 使用近似聚合函數: 對於允許有少量誤差的查詢場景,使用這些函數對查詢性能有大幅提升。比如使用approx_distinct() 函數比Count(distinct x)有大概2.3%的誤差
- 用regexp_like代替多個like語句: Presto查詢優化器沒有對多個like語句進行優化,使用regexp_like對性能有較大提升
- 使用Join語句時將大表放在左邊: Presto中join的默認算法是broadcast join,即將join左邊的表分割到多個worker,然后將join右邊的表數據整個復制一份發送到每個worker進行計算。如果右邊的表數據量太大,則可能會報內存溢出錯誤。
- 使用Rank函數代替row_number函數來獲取Top N
- UNION ALL 代替 UNION :不用去重
- 使用WITH語句: 查詢語句非常復雜或者有多層嵌套的子查詢,請試着用WITH語句將子查詢分離出來