目錄
可能所有的SQLboy剛接觸SQL語句的時候都是select xxx from xxx where xxx。在hive中,我們把這種語句稱為select-from-where型語句,也可稱為簡單SQL,這類簡單SQL是特指不含有變轉換函數,聚合函數,開窗函數和連線操作的SQL語句。
這類SQL主要特徵是隻有map階段,沒有reduce階段。
本文分析一下這類簡單SQL執行計劃和效能,讓我們從最基礎的SQL分析,hive簡單語句select from where 型語句效能分析,逐漸深入,進而學會分析複雜SQL的效能和執行計劃。
所有的複雜SQL(幾百行?上千行?)都是由一個個簡單SQL帶一些特殊函數堆疊而成的。
這類SQL語句通常只有select-from-where,沒有其他函數操作,或者操作符處理,例如字串擷取。
例1 不帶函數操作的select-from-where型簡單SQL。
-- 本文預設使用mr計算引擎
explain
-- 統計年齡等於30歲的所有暱稱
select age,nick from temp.user_info_all
where ymd = '20230505'
and age = 30;
執行執行計劃結果:
STAGE DEPENDENCIES:
Stage-1 is a root stage
Stage-0 depends on stages: Stage-1
STAGE PLANS:
Stage: Stage-1
Map Reduce
Map Operator Tree:
TableScan
alias: user_info_all
Statistics: Num rows: 32634295 Data size: 783223080 Basic stats: COMPLETE Column stats: NONE
Filter Operator
predicate: (age = 30) (type: boolean)
Statistics: Num rows: 16317147 Data size: 391611528 Basic stats: COMPLETE Column stats: NONE
Select Operator
expressions: 30 (type: bigint), nick (type: string)
outputColumnNames: _col0, _col1
Statistics: Num rows: 16317147 Data size: 391611528 Basic stats: COMPLETE Column stats: NONE
File Output Operator
compressed: true
Statistics: Num rows: 16317147 Data size: 391611528 Basic stats: COMPLETE Column stats: NONE
table:
input format: org.apache.hadoop.mapred.SequenceFileInputFormat
output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat
serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
Stage: Stage-0
Fetch Operator
limit: -1
Processor Tree:
ListSink
通過以上內容,我們可以看到,整個SQL邏輯執行過程中只有map操作樹(Map Operate Tree),若轉換成MapReduce來看的話,即只有Map階段的任務。
如果有執行計劃裡關鍵詞不熟悉的,建議閱讀這篇 Hive執行計劃之一文讀懂Hive執行計劃 。
以上流程我們可以分解為執行邏輯圖來看,如下圖:
我們在之前的文章中提起過,Hive執行計劃是一個預估的執行計劃,只有在SQL實際執行後才會獲取到真正的執行計劃。那我們來看看以上語句的實際執行控制檯列印過程。額,失算了,因為結果太多,限制一下輸出條數。
Query ID = hdfs_20230613111158_03c8f6e1-e04f-4e4e-aa9b-569a89860438
Total jobs = 1
Launching Job 1 out of 1
# 這裡表示沒有reduce任務,reduce任務執行的伺服器節點是0個。
Number of reduce tasks is set to 0 since there's no reduce operator
...
Hadoop job information for Stage-1: number of mappers: 6; number of reducers: 0
2023-06-13 11:12:28,564 Stage-1 map = 0%, reduce = 0%
2023-06-13 11:12:45,219 Stage-1 map = 17%, reduce = 0%, Cumulative CPU 6.17 sec
...
2023-06-13 11:12:54,523 Stage-1 map = 100%, reduce = 0%, Cumulative CPU 40.52 sec
MapReduce Total cumulative CPU time: 40 seconds 520 msec
Ended Job = job_1675664438694_14052273
MapReduce Jobs Launched:
Stage-Stage-1: Map: 6 Cumulative CPU: 40.52 sec HDFS Read: 203436481 HDFS Write: 2412 SUCCESS
Total MapReduce CPU Time Spent: 40 seconds 520 msec
從上面的結果可以知道,實際的執行過程也是隻有map階段的操作。
針對select-from-where只有map階段操作而沒有reduce階段的主要原因是這類SQL只有從表中讀取資料並執行資料行的過濾,並沒有需要將HDFS在其他節點上的資料與該節點資料放在一起處理的必要,因此這類SQL不需要reduce操作。Map階段過濾後的資料,就是最終的結果資料。
這種只含map的操作,如果檔案大小控制在合適的情況下,都將只有本地操作,其執行非常高效,執行效率完全不輸於在計算引擎Tez和Spark上執行。感興趣的小夥伴可以去將三者執行效率比對一下。
接下來我們再以mr虛擬碼的方式理解一下上述語句的執行情況:
例2 MRselect-from-where簡單SQL程式碼解析
map(inkey,invalue,context);
colsArray = invalue.split("\t");
//對應filter操作,過濾掉age=30的資料行,ymd為分割區列,屬於檔案級操作,這裡不展示了。
if int(colsArray[11]) == 30 {
//獲取age,nick兩列,就是投影操作,即select操作
age = colsArray[11];
nick = colsArray[7];
//最後輸出兩列age,nick,執行計劃中對應的為_col0和_col1.這裡invalue為1
context.write(age,nick);
}
reduce(inkey,invalue,context)
//pass表示不會執行
pass;
這裡的普通函數特指除錶轉換函數(UDTF),聚合函數和視窗函數之外的函數。例如:nvl(),cast(),case when,concat(),year()等,具體有哪些,後續會專門羅列。
這類SQL也屬於select-from-where型SQL,其主要特點也是隻有map階段處理。
我們也可以給它更具體的稱為 select-function(column)-from-where-function(column)類。
接下來可以看一個帶普通函數和操作符的SQL執行計劃案例。
例3 帶普通函數和操作符的SQL執行計劃。
explain
-- 統計年齡等於30歲的所有暱稱
select uid,
nvl(client,'android') as client,
case when age > 20 then '老臘肉' else '小鮮肉' end as label,
concat(nick,'_測試') as nick,
cast(chat_uv as double)/10 as chat
from temp.user_info_all
where ymd = '20230505'
and age in (18,19,20,21) and chat_uv is not null and substr(uid,0,1) = '1';
輸出的執行計劃結果:
STAGE DEPENDENCIES:
Stage-1 is a root stage
Stage-0 depends on stages: Stage-1
STAGE PLANS:
Stage: Stage-1
Map Reduce
Map Operator Tree:
TableScan
alias: user_info_all
Statistics: Num rows: 32634295 Data size: 783223080 Basic stats: COMPLETE Column stats: NONE
# where 條件過濾
Filter Operator
predicate: ((age) IN (18, 19, 20, 21) and chat_uv is not null and (substr(uid, 0, 1) = '1')) (type: boolean)
Statistics: Num rows: 8158574 Data size: 195805776 Basic stats: COMPLETE Column stats: NONE
# 列投影
Select Operator
expressions: uid (type: bigint), NVL(client,'android') (type: string), CASE WHEN ((age > 20)) THEN ('老臘肉') ELSE ('小鮮肉') END (type: string), concat(nick, '_測試') (type: string), (UDFToDouble(chat_uv) / 10) (type: double)
outputColumnNames: _col0, _col1, _col2, _col3, _col4
Statistics: Num rows: 8158574 Data size: 195805776 Basic stats: COMPLETE Column stats: NONE
File Output Operator
compressed: true
Statistics: Num rows: 8158574 Data size: 195805776 Basic stats: COMPLETE Column stats: NONE
table:
input format: org.apache.hadoop.mapred.SequenceFileInputFormat
output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat
serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
Stage: Stage-0
Fetch Operator
limit: -1
Processor Tree:
ListSink
通過以上執行計劃我們可以看到,這個結果同select-from-where 型SQL,只有map階段的操作,如果實際去執行以上任務,得到的執行步驟也和例1類似。即在map執行完整個作業任務結束。
結合以上範例我們可以得出一個結論 select-function(colums)-from-where-function(column)這種型別的SQL可以歸於select-from-where類簡單SQL型別。
例4 例2的MapReduce虛擬碼執行邏輯。
//整個程式只有map階段,沒有reduce邏輯
map(inkey,invalue,context);
//資料輸入是一行資料
colsArray = invalue.split("\t");
if age in (18,19,20,21) and chat_uv != null and substr(uid, 0, 1) == '1'{
uid = colsArray[0];
client = colsArray[3];
if client == null{
client = 'android';
}
label = '';
if age > 20 {
label = '老臘肉';
} else {
label = '小鮮肉';
}
nick = nick+'_測試');
chat = double(chat_uv)/10;
}
context.write(uid,client+'\t'+label+'\t'+nick+'\t'+chat);
下一期:Hive常見時間函數的使用與問題整理
按例,歡迎點選此處關注我的個人公眾號,交流更多知識。
後臺回覆關鍵字 hive,隨機贈送一本魯邊備註版珍藏巨量資料書籍。