使用Presto SQL一些常見問題總結

查詢速度慢, 如何優化?

解決方法1: 避免單節點處理

雖然Presto是分佈式查詢引擎, 可是一些操做是必須在單節點中處理的. 例如:sql

  • count(distinct x)網絡

    • 考慮使用approx_distinct(x)代替
    • 可是須要注意這個函數有個大約在2.3%的標準偏差, 若是須要精確統計的狀況, 請繞道.
  • UNIONsession

    UNION有個功能是: 若是兩條記錄同樣, 會只保留一條記錄(去重).
    • 若是不考慮去重的狀況, 請使用UNION ALL
  • ORDER BYapp

    Presto對數據排序是做用在單節點上的
    • 若是要排序的數據量超過百萬行, 要謹慎考慮. 若是非要排序,儘可能將排序的字段減小些.

解決方法2: 減小表掃描的範圍

經過添加條件達到減小表掃描的範圍.分佈式

也能夠考慮將大數據量的表, 水平查分, 經過查不一樣的表分區達到效果.函數

解決方法3: 避免使用 SELECT * FROM

要明確寫出全部要訪問的列, 能加快速度.
例如性能

SELECT * FROM my_table

改爲:大數據

SELECT id, name, address FROM my_table

解決方法4: 將幾個LIKE語句放到函數regexp_like()

Presto的查詢優化器不能改善許多LIKE語句使用的地方, 致使這樣的語句查詢速度慢.優化

例如ui

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

若是左表和右表都比較大怎麼辦?

  1. 修改配置distributed-joins-enabled (presto version >=0.196)
  2. 在每次查詢開始使用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語句,這個魔術註釋將被忽略。

如何拼接字符串

解決方法:用 || 運算符

SELECT 'hello ' || 'presto'

如何在字段包含NULL的狀況下 添加default value

解決方法:用COALESCE(v1,v2,...)函數

-- This retuns 'N/A' if name value is null
SELECT COALESCE(name, 'N/A') FROM table1

如何從兩個數中選出最大/最小值

解決方法:用greatest / least 函數

SELECT greatest(5, 10) -- returns 10

Binary函數的應用

這裏主要的問題是:如何將binary/varbinary類型轉換爲varchar類型

轉換SHA256/MD5

SELECT to_hex(sha256(to_utf8('support@treasure-data.com'))) as email

SELECT to_hex(md5(to_utf8('support@treasure-data.com'))) as email

轉換base64

SELECT to_base64(to_utf8('support@treasure-data.com')) as email
=> "c3VwcG9ydEB0cmVhc3VyZS1kYXRhLmNvbQ=="
SELECT FROM_UTF8(from_base64('c3VwcG9ydEB0cmVhc3VyZS1kYXRhLmNvbQ=='))
=> "support@treasure-data.com"
相關文章
相關標籤/搜索