理解Google Spanner(4): 看懂查詢計(jì)劃

Spanner會為每條SQL生成一個或多個查詢計(jì)劃吼拥,并選擇數(shù)據(jù)庫認(rèn)為最優(yōu)的那個查詢計(jì)劃去執(zhí)行,同一個SQL线衫,不同的查詢計(jì)劃最終的效率可能是千差萬別的凿可,理解查詢計(jì)劃是SQL優(yōu)化的基本必備技能。

Spanner本身有官方文檔幫助大家理解查詢計(jì)劃,但是講得比較精簡枯跑,如果對Spanner不熟悉惨驶,可能理解起來比較困難,本文是這篇文檔的擴(kuò)展敛助,但是會更淺顯易懂粗卜、詳細(xì),有一些總結(jié)與延伸纳击。

本文不會講什么:

  1. 查詢運(yùn)算符詳解(Query Operators)续扔,請自行參閱Spanner文檔

本文會講什么:

  1. 理解Spanner如何執(zhí)行一個查詢計(jì)劃
  2. 如何看懂GCP Console下獲取的Spanner的查詢計(jì)劃
  3. 如何基于查詢計(jì)劃作出優(yōu)化

一、查詢計(jì)劃如何被執(zhí)行

Spanner是分布式數(shù)據(jù)庫焕数,因此一個數(shù)據(jù)庫實(shí)例(Instance)是分布在多臺server的纱昧,因此一條SQL可能意味著需要多臺server配合才能產(chǎn)生最終結(jié)果。
client連接到Spanner堡赔,Spanner將SQL解析為查詢計(jì)劃(Query Plan)识脆,并選擇一臺server作為root server,Spanner將plan發(fā)送到root server加匈,其他需要參與query的server稱為remote server存璃,均被root server協(xié)調(diào)仑荐,它們接收root server下發(fā)的subplan雕拼,然后將查詢結(jié)果返回給root server,最終由root server返回給client粘招。
Root server本身也參與query啥寇,因此理論上有一部分subplan會下發(fā)給自己,也就是說root server本身也可以扮演remote server洒扎。
Root Server下發(fā)subplan到各個Remote Server并從Remote Server收集結(jié)果的行為辑甜,在查詢計(jì)劃中稱為Distributed Union

查詢計(jì)劃如何被分發(fā)

由于每臺server都負(fù)責(zé)保存多個splits袍冷,因此每臺remote server收到subplan后磷醋,會將subplan再次分割為一到多個splits的查詢計(jì)劃,下發(fā)給特定的split胡诗,每個split獨(dú)立執(zhí)行自己的計(jì)劃并返回結(jié)果給server邓线,這個過程在查詢計(jì)劃中稱為Local Distributed Union

image.png

總結(jié)一下:
Root Server負(fù)責(zé):

1. 下發(fā)subplan到其他參與的server
2. 等待所有server返回subplan結(jié)果給自己
3. 匯總各個server的執(zhí)行結(jié)果煌恢,如果需要的話骇陈,進(jìn)行進(jìn)一步處理
4. 將匯總后的執(zhí)行結(jié)果返回給client

Remote Server負(fù)責(zé):

1. 接收Root Server下發(fā)的subplan
2. 將subplan拆分成1個或多個分片的subplan并執(zhí)行
3. 匯總各個分片執(zhí)行的結(jié)果
4. 返回匯總的結(jié)果給Root Server

二、解讀查詢計(jì)劃

1. Example — 簡單查詢計(jì)劃

下圖是摘自Spanner官方文檔的查詢計(jì)劃
圖中箭頭由下往上瑰抵,表示的是結(jié)果返回順序你雌,而查詢計(jì)劃的分發(fā)順序恰恰相反,應(yīng)該由上往下二汛。

摘自Spanner官方文檔的查詢計(jì)劃
下發(fā)階段

圖中的查詢計(jì)劃表示SQL被解析為查詢計(jì)劃婿崭,發(fā)送給Root Server拨拓,Root Server進(jìn)行Distriubted Union將subplan下發(fā)給Remote Server并等待最終結(jié)果。
Serialize ResultAggregate都是對結(jié)果進(jìn)行處理的運(yùn)算符逛球,因此下發(fā)期間可以忽略千元。
Remote Server(s)收到Root Server的subplan后,將subplan拆分為特定split(s)的查詢計(jì)劃颤绕,交給特定的split(s)執(zhí)行幸海,也就是Local Distributed Union
Local Distributed Union下就是每個split會進(jìn)行的查詢計(jì)劃奥务,此時(shí)查詢計(jì)劃分發(fā)完畢物独,我們開始從下往上讀,解讀執(zhí)行與返回過程氯葬。

執(zhí)行與返回階段

每個split執(zhí)行Table Scan挡篓,從Songs表讀取SingerId。
每一個被讀出的SingerId都會被Filter操作符根據(jù)SingerId<100的條件過濾帚称,只有滿足條件的官研,才會往上返回。
Filter返回的數(shù)據(jù)會在Remote Server進(jìn)行Local Distributed Union闯睹,也就是結(jié)果集的合并戏羽,并且再往上返回。
所有Remote Server都會將結(jié)果返回給Aggregator操作符楼吃,進(jìn)行結(jié)果集的聚合始花。
聚合后的結(jié)果被Serialize Result操作符組合為最終返回格式,這個操作符是每個查詢計(jì)劃都會有的孩锡,負(fù)責(zé)將查詢出的數(shù)據(jù)轉(zhuǎn)換為要發(fā)送回client的格式酷宵。
轉(zhuǎn)換為最終格式的數(shù)據(jù),進(jìn)行Distributed Union躬窜,返回SQL執(zhí)行的最后結(jié)果浇垦。

整個查詢計(jì)劃結(jié)束

2. Example — 復(fù)雜查詢計(jì)劃

上面的簡單查詢計(jì)劃只包括一元操作符,下面講一下包含二元操作符的查詢計(jì)劃荣挨,比如進(jìn)行Join操作男韧。
注意:下圖生成的查詢計(jì)劃有個前提條件—— Albums表是Singers表的子表,兩者是Interleave關(guān)系垦沉。


摘自Spanner官方文檔的復(fù)雜查詢計(jì)劃
下發(fā)階段

任何查詢計(jì)劃的分發(fā)都是差不多的煌抒,只有4個操作符涉及分發(fā),那就是Distributed Union厕倍、Distributed Cross Apply寡壮、Distributed Outer ApplyLocal Distributed Union,因此這里不再講一遍。

執(zhí)行與返回階段

最底部是兩個并排的查詢計(jì)劃况既,應(yīng)該從左往右看这溅,左邊是input,右邊是對input進(jìn)行map處理棒仍,也就是說悲靴,查詢計(jì)劃是從下往上執(zhí)行,從左往右執(zhí)行莫其。
先對Albums表進(jìn)行Table Scan查出SingerId癞尚、AlbumId、AlbumTitle三個字段乱陡。
Table Scan的結(jié)果會返回給Cross Apply操作符浇揩,此操作符對結(jié)果進(jìn)行map,也就是為每個結(jié)果執(zhí)行一次Index Scan憨颠。
Index Scan查出SongName返回給Cross Apply胳徽。
Cross Apply 將Table Scan與Index Scan的結(jié)果進(jìn)行Join,實(shí)際上Cross Apply操作符就是進(jìn)行nested loop join爽彤,由于兩個參與Join的表是Interleave的养盗,所以此Cross Apply只需要在本Remote Server上執(zhí)行,否則應(yīng)使用Distributed Cross Apply(將在下一個例子中說明)适篙。
Join后的結(jié)果被Serialize Result轉(zhuǎn)換為返回格式往核。
Local Distributed Union整合此Remote Server上的所有Results返回給Root Server。
Root Server進(jìn)行Distributed Union將最終結(jié)果返回匙瘪。

整個查詢計(jì)劃結(jié)束铆铆。

3. Example — Distributed Cross Apply查詢計(jì)劃

摘自Spanner官方文檔的Distributed Cross Apply

上圖中的SQL需要讀2張表蝶缀,一張是索引SongsBySongName丹喻,一張是數(shù)據(jù)表Songs,索引無法Interleave翁都,所以索引和數(shù)據(jù)可以分別處于不同的分片碍论,那么要實(shí)現(xiàn)這個SQL,就不能使用(Local) Cross Apply柄慰,而需要使用Distributed Cross Apply鳍悠,因此最頂層的操作符是Distributed Cross Apply

下發(fā)階段

Root Server的Distributed Cross Apply會等待Distributed Union后進(jìn)行Create Batch的結(jié)果作為input坐搔,當(dāng)Distributed Cross Apply收到Create Batch的結(jié)果作為input后藏研,再下發(fā)plan給Remote Server,做map操作概行。
這里注意蠢挡,下發(fā)其實(shí)被分為了兩個階段,左邊先執(zhí)行完,Distributed Cross Apply才會進(jìn)行右邊的下發(fā)业踏。

執(zhí)行與返回階段

Remote Server將plan分配給多個splits進(jìn)行Index Scan禽炬。
Index Scan的結(jié)果被Filter過濾符合條件的返回。
Local Distributed Union匯總本臺server上的數(shù)據(jù)發(fā)回給Root Server勤家。
Root Server使用Distributed Union匯總Remote Server發(fā)來的數(shù)據(jù)腹尖。
Serialize Result格式化數(shù)據(jù)。
Create Batch操作符代表創(chuàng)建中間表伐脖,因?yàn)樯婕暗娇鐂erver的join热幔,因此需要創(chuàng)建中間表。
將Create Batch創(chuàng)建的中間表作為input發(fā)給Distributed Cross Apply操作符讼庇。
Distributed Cross Apply下發(fā)查詢到Remote Server(進(jìn)行Map)断凶。
Batch Scan讀取中間表并返回給Cross Apply
Cross Apply根據(jù)Batch Scan結(jié)果進(jìn)行Join并通過Serialize Result巫俺、Local Distributed Union后返回給Root Server认烁。
Distributed Cross Apply根據(jù)返回結(jié)果完成Join,返回SQL執(zhí)行結(jié)果介汹。

整個查詢計(jì)劃結(jié)束却嗡。

4. 從GCP Console解讀查詢計(jì)劃

在GCP Console中可以方便地獲得查詢計(jì)劃,但不是圖片形式嘹承,沒有左右關(guān)系窗价,因此我們需要將Console中的text展示的查詢計(jì)劃,在腦袋中轉(zhuǎn)換為圖片版的叹卷。


Console查詢計(jì)劃

每行計(jì)劃的開頭都有一個小標(biāo)記撼港。
轉(zhuǎn)換原則是:

  • 垂直箭頭則表示上下關(guān)系。比如:


    上下關(guān)系
  • 人字型箭頭代表這是一個接收多個參數(shù)的操作符骤竹,比如Hash Join


    Hash Join是二元操作符
  • 直角箭頭代表是父操作符的輸入?yún)?shù)帝牡,比如圖中兩個Distributed Union不是上下級關(guān)系,而是兄弟關(guān)系蒙揣,作為Hash Join的下級操作符靶溜,也就是Hash Join的輸入?yún)?shù),兩個Distributed Union應(yīng)該是左右排列懒震。

  • 對于應(yīng)該左右排列的操作符罩息,越上面出現(xiàn),越左邊个扰,從左往右依次排放瓷炮。


    左右關(guān)系

因此上圖應(yīng)該是如下:


查詢計(jì)劃圖

三、SQL優(yōu)化

我們可以根據(jù)查詢計(jì)劃對SQL進(jìn)行優(yōu)化递宅,但是在優(yōu)化之前務(wù)必盡量讀懂查詢計(jì)劃娘香,因此需要了解每個操作符的意義冬筒,在進(jìn)行下面的閱讀前最好能夠先閱讀Spanner操作符文檔

1. 為什么用了索引還是慢查詢茅主?

大家都知道全表掃描是嚴(yán)格禁止的(數(shù)據(jù)量特別小的表不在討論范圍)舞痰,導(dǎo)致慢查詢甚至拖垮數(shù)據(jù)庫,于是往查詢上面加索引诀姚,結(jié)果加了索引還是慢查詢响牛。
為什么會出現(xiàn)這種情況,是因?yàn)榇蠹液雎粤藢?dǎo)致慢查詢的根本原因——大量磁盤IO導(dǎo)致CPU和內(nèi)存被大量占用赫段,全表掃描不用說呀打,一定是大量的磁盤IO,把表依次讀一遍糯笙,實(shí)際上索引建得不好贬丛,也會有這種情況。
在Spanner中给涕,索引也是表豺憔,索引不過是只存儲部分字段的表而已,可以理解為一個比數(shù)據(jù)表更小的表够庙,如果查詢條件不能利用索引的最左前綴原則恭应,那么這個索引就只能被全索引掃描,Spanner會將索引全部掃描一遍耘眨,利用Filter返回符合條件的行昼榛,對CPU和內(nèi)存的占用極大。
比如為users表建立一個 (user_name,email) 的索引剔难,卻使用這個索引進(jìn)行 SELECT user_name FROM users WHERE email = xxx 查詢胆屿,由于查詢條件不包括user_name,因此無法使用這個Index進(jìn)行Filter Scan偶宫,也就是無法直接定位到索引所在數(shù)據(jù)頁非迹,而需要讀取整個索引,進(jìn)行Filter操作读宙,也就是全表掃描(索引也是表彻秆,因此對表和索引的全掃描都可以稱為全表掃描)楔绞。
從Spanner的查詢計(jì)劃中可以看到是否對一個索引或者一個表使用了全表掃描结闸,如下圖:

Full Scan

如果索引中有100萬條記錄,那么100萬條都會被讀入內(nèi)存進(jìn)行Filter酒朵,CPU和內(nèi)存壓力比較大桦锄,會出現(xiàn)慢查詢,因此對于查詢計(jì)劃中的 Full Scan 需要根據(jù)SQL運(yùn)行頻率蔫耽、表大小進(jìn)行評估结耀,在必要的情況下建立更合適的索引避免 Full Scan留夜。
與Full Scan相對應(yīng)的是Filter Scan,也就是直接定位到索引數(shù)據(jù)所在數(shù)據(jù)頁图甜,只讀取符合條件的索引碍粥。
注意,F(xiàn)ilter Scan與Filter是完全不一樣的黑毅,詳見官方文檔嚼摩。
Filter Scan

2. Apply Join與Hash Join的選擇

Apply Join

也就是Nested Loop Join,接收一組記錄作為input矿瘦,然后分別對每條input進(jìn)行Join枕面,具體原理可以在網(wǎng)上搜到,這里就不多說缚去。
操作符Cross Apply即代表Apply Join潮秘,它好處是,input越小易结,需要進(jìn)行的join記錄數(shù)越少枕荞,讀取越少,速度越快搞动。
可以說Apply Join是基于記錄的(record-based)买猖。

Hash Join

與Apply Join相反,Hash Join是基于集合(set-based)的滋尉,對于參與Join的兩張表玉控,會選擇更小的那一張,完全加載入內(nèi)存狮惜,建立一個Hash表高诺,再讀取另一張表,匹配Hash完成Join碾篡。
可以通過這篇文章理解Hash Join:《如何在分布式數(shù)據(jù)庫中實(shí)現(xiàn) Hash Join?》

綜上所述虱而,Hash Join適合需要整張表參與的大數(shù)據(jù)集的Join,而Apply Join適合記錄較少的Join开泽。
如果WHERE條件篩選后只有少量記錄牡拇,那么Apply Join是更好的選擇,此時(shí)如果選擇Hash Join穆律,即使某張表被篩選出少量記錄惠呼,另一張表還是會被全表讀取,效率非常低峦耘。

3. 本機(jī)Join可減少開銷

本機(jī)的Join比Distributed Join更快剔蹋、開銷更少,比如Cross Apply比Distributed Cross Apply更快辅髓,因此對于常用的Join泣崩,優(yōu)化思路是進(jìn)行本機(jī)Join避免Distributed Join少梁。
Interleave是記錄co-located的強(qiáng)保證,因此必要的情況下矫付,可以使用Interleave提升Join效率凯沪。
但是要注意Interleave的co-located保證也導(dǎo)致熱點(diǎn)不能被分散,因此需要綜合業(yè)務(wù)考慮后再決定是否使用Interleave买优。

4. 測試比Explaination更重要

查詢計(jì)劃不是萬能的著洼,特別是僅僅使用Spanner Console的Explaination Only功能,是看不到最終掃描行數(shù)和執(zhí)行效率的而叼,對于查詢計(jì)劃的分析僅僅限于理論身笤,理論必須結(jié)合實(shí)踐,因此非常有必要在測試環(huán)境模擬足夠的數(shù)據(jù)量去進(jìn)行測試葵陵、調(diào)優(yōu)液荸、驗(yàn)證。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脱篙,一起剝皮案震驚了整個濱河市娇钱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绊困,老刑警劉巖文搂,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秤朗,居然都是意外死亡煤蹭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門取视,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硝皂,“玉大人,你說我怎么就攤上這事作谭』铮” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵折欠,是天一觀的道長贝或。 經(jīng)常有香客問我,道長锐秦,這世上最難降的妖魔是什么咪奖? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮农猬,結(jié)果婚禮上赡艰,老公的妹妹穿的比我還像新娘。我一直安慰自己斤葱,他們只是感情好慷垮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揍堕,像睡著了一般料身。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衩茸,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天芹血,我揣著相機(jī)與錄音,去河邊找鬼楞慈。 笑死幔烛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的囊蓝。 我是一名探鬼主播饿悬,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼聚霜!你這毒婦竟也來了狡恬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蝎宇,失蹤者是張志新(化名)和其女友劉穎弟劲,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姥芥,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兔乞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凉唐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片报嵌。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熊榛,靈堂內(nèi)的尸體忽然破棺而出锚国,到底是詐尸還是另有隱情,我是刑警寧澤玄坦,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布血筑,位于F島的核電站,受9級特大地震影響煎楣,放射性物質(zhì)發(fā)生泄漏豺总。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一择懂、第九天 我趴在偏房一處隱蔽的房頂上張望喻喳。 院中可真熱鬧,春花似錦困曙、人聲如沸表伦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹦哼。三九已至鳄哭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纲熏,已是汗流浹背妆丘。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留局劲,地道東北人勺拣。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像鱼填,于是被迫代替她去往敵國和親药有。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354