流量洪峰成为常态,腾讯数据库如何高性能支撑海量SQL查询?
今天分享的主題是TDSQL-SQL引擎架構(gòu)的演進(jìn)和查詢優(yōu)化實戰(zhàn)。今天分享分為四章,分別是:TDSQL簡介、SQL引擎簡介、SQL引擎查詢處理和最佳實踐。
點擊圖片收看直播回放及下載講師PPT
1. TDSQL分庫分表策略
前面的課程中,我們已經(jīng)對TDSQL的架構(gòu)做了比較詳細(xì)的介紹,這里我們簡單回顧一下TDSQL。TDSQL是騰訊針對金融行業(yè)推出的一款自主可控、高一致、分布式、HTAP數(shù)據(jù)庫產(chǎn)品,目前已為超過500+金融政企提供數(shù)據(jù)庫服務(wù),行業(yè)涵蓋了銀行、保險、證券信托、互聯(lián)網(wǎng)金融等等行業(yè)。
TDSQL是Shared-Nothing架構(gòu)的分布式數(shù)據(jù)庫,這張圖是TDSQL的核心架構(gòu)圖,在這個架構(gòu)圖中包含了三個重要組件,分別是SQL引擎、MetaCluster和后端SET。其中每個SET是由一主多備構(gòu)成的高可用復(fù)制單元,在一個TDSQL中往往包含多個這樣的SET。每一個SET負(fù)責(zé)存儲分布式表的部分分片信息。SQL引擎負(fù)責(zé)處理業(yè)務(wù)發(fā)過來的SQL請求,然后對SQL進(jìn)行拆分,發(fā)送給各個SET進(jìn)行處理,并對每個SET返回的結(jié)果進(jìn)行聚合,得到最終的結(jié)果。
在TDSQL中,每個SET都會負(fù)責(zé)一段連續(xù)的哈希;在TDSQL中建立的每一個分布式表,SQL引擎都要求用戶指定其中的一個列為分區(qū)鍵,SQL引擎通過計算這個分區(qū)鍵的哈希值,將這個表的每一行數(shù)據(jù)都映射到這個哈希空間,由對應(yīng)的SET進(jìn)行存儲。哈希空間與SET之間的對應(yīng)關(guān)系,我們叫做路由表。這個路由信息會存儲在MetaCluster里。當(dāng)對整個集群進(jìn)行縮容或擴容時,每個SET對應(yīng)的哈希空間也會發(fā)生變化,對應(yīng)的數(shù)據(jù)也會進(jìn)行搬遷。SQL引擎通過監(jiān)聽MC來感知集群的變化。
在建表的時候,SQL引擎通過關(guān)鍵字讓用戶在建表的時候指定其中的一個列為分區(qū)鍵。在這個例子中,用戶建立一張表,其中指定ID為shardkey,也就是說SQL引擎通過計算分區(qū)鍵ID的哈希值,將這個表的數(shù)據(jù)進(jìn)行打散,均勻的分布在各個SET上。
2. TDSQL-SQL引擎:如何優(yōu)雅處理海量SQL邏輯
前面我們回顧了一下TDSQL的整體架構(gòu),以及Sharding策略。這里我們再介紹一下SQL引擎。
目前TDSQL-SQL引擎已經(jīng)能夠支持絕大多數(shù)的MySQL語法,分布式查詢、事務(wù)和死鎖檢測。應(yīng)用程序端可以像使用單機數(shù)據(jù)庫一樣,通過SQL引擎來使用整個TDSQL。除了這些比較基本的功能,SQL引擎還支持一些比較高級的功能,例如SQL防火墻、讀寫分離等等。我們這里通過一條SQL的大體執(zhí)行路徑來看一下這些功能之間的關(guān)系:應(yīng)用程序通過MySQL客戶端向SQL引擎發(fā)送了一條SQL,SQL引擎通過協(xié)議解析,從數(shù)據(jù)包中得到這條SQL,并對這條SQL進(jìn)行語法解析,語法解析以后我們就得到一棵抽象的語法樹,如果應(yīng)用配置了SQL防火墻,我們還會匹配防火墻的規(guī)則,判斷這條SQL能否執(zhí)行。如果能夠執(zhí)行的話,接著我們會構(gòu)建這條SQL的分布式執(zhí)行計劃——在這個計劃里面,我們描述了由哪些SET參與查詢的執(zhí)行,每個SET應(yīng)該執(zhí)行什么樣的邏輯,SQL引擎又進(jìn)行怎樣的聚合,兩者之間怎么進(jìn)行協(xié)調(diào)。得到分布式執(zhí)行計劃以后,我們將這個計劃再轉(zhuǎn)換成SQL的形式發(fā)送給對應(yīng)的SET執(zhí)行。接著我們執(zhí)行讀寫分離算法,從每個SET中挑選出最適合訪問的實例,然后就可以向這個實例發(fā)送SQL了。我們收到每個SET反饋的響應(yīng)以后,對返回的結(jié)果進(jìn)行聚合。這里的聚合邏輯主要是執(zhí)行分布式執(zhí)行計劃分配給SQL引擎的一些任務(wù)。如果是一條簡單的查詢,我們通過流式聚合就可以得出最終的結(jié)果。并將結(jié)果反饋給應(yīng)用;如果是一條比較復(fù)雜的查詢,往往需要拆分成多個階段來執(zhí)行。這個時候就需要構(gòu)建下一個階段的分布式執(zhí)行計劃,然后再執(zhí)行這個計劃。經(jīng)過多次執(zhí)行,所有的階段全部執(zhí)行完畢之后,最終在本地的執(zhí)行器中進(jìn)行最終的計算和聚合,從而得到一個最終的結(jié)果。
3. TDSQL-SQL引擎查詢處理模型:如何實現(xiàn)高性能SQL引擎查詢
我們這里已經(jīng)介紹了SQL引擎的一個大體的功能,這里我們再具體介紹SQL引擎是怎么處理各類查詢的。我們將查詢分成兩類,一類是Select查詢,一類是更新操作,例如Delete、Update、Insert等等。對Select查詢的話,我們有兩種處理方式:一種是流式處理模型,一種是通用處理模型。流式處理模型主要是處理單表上的查詢,而通用處理模型是對流式處理模型的彌補,負(fù)責(zé)處理分布式的跨節(jié)點查詢。
3.1 流式處理模型原理及典型案例詳解
我們先介紹一下流式處理模型。這里有一個簡單的例子,
在這個例子里面用戶訪問的是一個日志表,這個日志表里面有三個列:其中ID是日志的ID,name是用戶名,score是用戶的歷史游戲得分。一個用戶會有多個游戲得分,并且他的游戲得分的記錄會分散在各個SET上。業(yè)務(wù)想要獲取每個用戶的平均分,SQL引擎不能直接把SQL發(fā)送給各個SET,所以他需要計算出每個用戶的總得分和這個用戶在這張表上的一個總記錄數(shù),然后用兩者的商來求得每個用戶的平均分——具體來說SQL引擎要求每個SET返回,每個用戶在每個SET上的一個局部的總得分和總記錄數(shù)。接著SQL引擎將每個SET上返回的結(jié)果再進(jìn)行聚合,得到每個用戶的總得分和總記錄數(shù),然后再用兩者相除。
我們發(fā)現(xiàn),SET執(zhí)行的SQL里面有一個order ?by,這個是做什么的呢。我們假設(shè)沒有這個order ?by,那么某一個用戶張三,SET1反饋的第一條記錄是張三的局部得分,SQL引擎拿到這個張三的結(jié)果以后,無法直接計算出張三這個用戶的平均分,他還需要知道張三在SET2上的得分。但是SET2可能在最后一條記錄才返回張三在SET2的局部的得分,所以SQL引擎不得不進(jìn)行等待操作,等待SET2返回所有的結(jié)果,而這個等待操作會非常影響性能。如果我們增加了這個order ?by,SET1和SET2返回的結(jié)果都是按名字有序地進(jìn)行,SQL引擎進(jìn)行一個簡單的Merge就可以得到一個全局有序的結(jié)果。對張三這個用戶來說,這個有序的結(jié)果里面,他在SET1和SET2上的局部得分就是相鄰的,所以SQL引擎不需要等待SET1和SET2反饋的所有結(jié)果,他直接就能計算出張三的平均分。這個引擎計算出張三的平均分以后,立馬就可以將張三的得分再反饋給業(yè)務(wù)。
在這個例子中可以看到,SET1和SET2不停地向SQL引擎返回數(shù)據(jù),SQL引擎隨即對返回的結(jié)果進(jìn)行聚合,并將聚合結(jié)果立即返回給這里。整個過程就像流水一樣,所以我們將這個過程稱之為流式處理模型。流式處理模型最核心的地方就是對SQL進(jìn)行拆分——拆分成兩部分,一個是SET執(zhí)行的部分,以及在SQL引擎執(zhí)行的部分。就拿剛剛的例子來說,SET進(jìn)行一個局部的聚合,計算出每個用戶在SET上的局部的總得分和總記錄數(shù)。SQL引擎進(jìn)行一個全區(qū)的累加和聚合,得到每個用戶的總得分和總記錄數(shù)。進(jìn)行拆分以后,流式處理模型還會添加一些額外的操作,將兩者銜接起來——例如剛剛例子中的order ?by,這樣一來我們就得到了一個流式處理的執(zhí)行計劃。當(dāng)然除了這個以外,我們還會進(jìn)行一些優(yōu)化,將原本分配給SQL引擎的一些操作進(jìn)行下推,例如模型中的Limit和distinct,其實它應(yīng)該在SQL引擎上執(zhí)行的。我們通過把這些下推,能夠較大地減少SET返回給SQL引擎的數(shù)據(jù)量,從而也就提升了整個執(zhí)行的性能。
剛才我們一直強調(diào)流式處理模型是針對單表處理的,事實上它還能夠處理其他的場景。
例如這個例子1中的表,兩張表的shardkey相等,因此我們可以把t1、t2當(dāng)成一個整體,當(dāng)成一個整體之后我們就可以用剛剛介紹的查詢拆分構(gòu)建執(zhí)行計劃,并用流式處理模型進(jìn)行處理了。對子查詢的話,類似這里的IN,a是T1和B1的shardkey,這個子查詢同樣暗含著T1的shardkey等于B的shardkey,我們同樣將T1和這個子查詢當(dāng)成一個整體,套用剛剛介紹的查詢拆分和流式處理模型進(jìn)行處理。當(dāng)然,并不是所有的查詢都能夠滿足這樣的一個情況,如果兩個表不是shardkey相等的話,這個時候我們就需要用后面的通用處理模型進(jìn)行處理了。但是通用處理模型的性能比不上流式處理模型,那怎樣才能夠?qū)⒘魇教幚砟P瓦M(jìn)行進(jìn)一步推廣呢?TDSQL就引入了一個廣播表——廣播表的意思就是說將一些表的數(shù)據(jù)全量備份在每個節(jié)點上,這樣一來原本T1、T2的關(guān)聯(lián)查詢,他們必須用通用處理模型進(jìn)行處理,這個時候我們也可以使用流式處理模型進(jìn)行處理了。
從剛剛的介紹,我們可以看到流式處理模型是以pipeline的方式執(zhí)行,SET不斷返回數(shù)據(jù)給SQL引擎,SQL引擎不停地對返回的結(jié)果進(jìn)行聚合,并將聚合的結(jié)果立即返回給應(yīng)用。所以說在這種處理方式下,SQL引擎的內(nèi)存開銷是非常低的。同時在執(zhí)行的過程中,各個SET是并發(fā)執(zhí)行,這也保證了流式處理模型的性能、并行性。流式處理模型最核心的一方就是拆分和下推——通過智能的拆分和下推,我們將大量的計算下推給SET執(zhí)行,這樣就大大減少了SQL引擎需要從SET獲取的數(shù)據(jù)。流式處理模型雖然要求比較嚴(yán)苛,但是我們通過合理的選擇shardkey以及引入廣播表,事實證明流式處理模型其實能夠滿足絕大多數(shù)的OLTP業(yè)務(wù)。
3.2 通用處理模型:跨節(jié)點分布式查詢優(yōu)化的利器
接下來我們介紹一下SQL引擎的通用處理模型。
通用處理模型主要是針對分布式的跨節(jié)點查詢。說到分布式的跨節(jié)點查詢,大家首先想到的肯定是Spark、Greenplum這樣的大數(shù)據(jù)處理引擎。在這些引擎中,他們執(zhí)行查詢的時候往往需要在節(jié)點之間進(jìn)行大規(guī)模的數(shù)據(jù)搬遷,因為TDSQL主要針對OLTP業(yè)務(wù),所以在生產(chǎn)上,TDSQL往往運行著大量的交易SQL,進(jìn)行大規(guī)模數(shù)據(jù)搬遷肯定是不現(xiàn)實的。所以,TDSQL基于常見的OLTP場景而設(shè)計了自己的分布式處理查詢框架。在這個模型下,SQL引擎對SQL進(jìn)行語法解析以后,就將實際參與查詢的數(shù)據(jù)加載到SQL引擎,然后在本地執(zhí)行這個查詢。在我們所設(shè)想的OLTP場景下,用戶常常為一個表的索引指定一個常量或者常量的范圍,表之間的連接也基本上為等值連接。在這樣的一個場景下,SQL引擎通過查詢下推和條件下推大大降低了需要加載的數(shù)據(jù)量,從而使得這個方案變得切實可行。
這里我們以一個例子來講解通用數(shù)據(jù)模型是怎樣處理一條SQL的。
在這個例子中一共有三張表,每個表中的shardkey我們都設(shè)置為A。SQL先將T1、T2進(jìn)行一個等值連接,然后where條件指定了T1.A是一個常量;在這里還有一個子查詢——這個子查詢是一個相關(guān)子查詢,它引用了外層的T1.C。在執(zhí)行這條查詢的時候,SQL引擎首先選擇一個需要加載的表,在這里我們選擇了T1.A,因為它包含了一個可以下推條件,而且條件是一個主鍵。我們加載完T1以后,就可以知道T1.B和T1.C是兩個列的范圍了,根據(jù)這個連接的連接條件,我們可以推算出T2.A的范圍。根據(jù)推算出來的T2.A的范圍我們繼續(xù)加載T2。這樣一來,就可以將上層涉及的兩個表T1、T2都加載到本地。接著我們處理內(nèi)層的子查詢,這個子查詢是一個相關(guān)子查詢,如果我們在加載T2的時候發(fā)現(xiàn)T1.C其實是一個常量,這個時候就可以將這個相關(guān)子查詢轉(zhuǎn)換成一個非相關(guān)子查詢,并將這個子查詢提取出來進(jìn)行獨立計算。計算以后我們將它的結(jié)果來替換這個子查詢;如果它不是一個常量的話,我們就利用這個條件推斷出T3.A的范圍,再用推算出來的這個條件去加載T3,這樣一來,這條SQL所設(shè)計的所有的表的數(shù)據(jù),我們都加載到了SQL引擎,接下來就像MySQL一樣執(zhí)行這條查詢就可以了。
通過這個例子我們可以看到,SQL引擎通過條件下推,并利用加載的數(shù)據(jù)推算出新的條件,然后利用新的條件再去過濾我們需要加載的表,這樣可以大大降低需要加載的數(shù)據(jù)量。對于子查詢的話,如果我們發(fā)現(xiàn)它能夠轉(zhuǎn)換成非相關(guān)詞查詢,這個時候我們還會進(jìn)行子查詢的下推。當(dāng)然具體優(yōu)化的技術(shù)還有很多,這里我們就不再一一列舉。
我們將優(yōu)化和工程實踐中所使用的一些技術(shù)進(jìn)行一個分類,主要包括邏輯優(yōu)化、條件下推和隔離。
邏輯優(yōu)化:主要是對SQL的結(jié)構(gòu)進(jìn)行優(yōu)化,使它變得更加容易處理。例如我們將左/右連接轉(zhuǎn)換成內(nèi)連接。如果它能夠轉(zhuǎn)換——在原來的邏輯下需要先加載左連接的左表,然后才能加載左連接的右表。最后轉(zhuǎn)換以后,我們就可以消除這種限制,能夠生成多種加載方式,這樣的話我們就可以有更好的一個優(yōu)化的空間。
條件下推:是指用一些手段將SQL中的條件提取出來,或者說我們利用已知的一些信息推斷出新的條件,用這些推斷出來的條件來過濾需要加載的數(shù)據(jù)。同時我們還可以通調(diào)整加載的順序,例如剛剛例子中我們優(yōu)先加載了T1,利用優(yōu)先加載小表的策略同樣能夠起到降低需要加載的數(shù)據(jù)量的作用。
隔離:TDSQL主要針對OLTP業(yè)務(wù),所以它線上會運行著大量的交易SQL,這個時候如果應(yīng)用發(fā)送過來的是一個復(fù)雜的查詢、是一個OLAP類的查詢的話,往往需要加載很大的數(shù)據(jù)量。而且執(zhí)行的時候也會有很大的CPU開銷,為了不影響這些正在運行的OLTP業(yè)務(wù),我們需要進(jìn)行隔離。比如說我們將比較耗時的操作,例如把數(shù)據(jù)加載過來,然后緩存在本地的臨時表里,以及最終要執(zhí)行查詢的時候,也將這個比較耗時的操作放在后臺異步執(zhí)行,這樣就可以對OLTP業(yè)務(wù)進(jìn)行隔離,避免影響在線的一個交易。
3.2.1通用處理模型總結(jié)
通過前面我們可以看到通用處理模型的SQL兼容性是非常好的,因為我們只需要將數(shù)據(jù)加載到本地,然后用傳統(tǒng)的方法執(zhí)行這個查詢就可以了。然后我們對OLTP場景有很多的優(yōu)化策略,通過這些策略,我們大大降低了需要加載的數(shù)據(jù)量。同時,這個通用處理模型也可以作為OLAP來用,如果當(dāng)做OLAP來用的話,我們會將數(shù)據(jù)加載和執(zhí)行這兩個比較耗時的動作在后臺異步執(zhí)行。
3.3 TDSQL-SQL引擎更新操作原理
前面我們講解了SQL引擎如何處理Select的查詢。這里我們再簡單介紹一下SQL引擎是如何處理更新操作的。
3.3.1 簡單更新操作
對于一些簡單的更新操作,例如這里的Insert、Update語句,我們根據(jù)SQL中的shardkey對這些SQL進(jìn)行拆分。例如INSERT,它插入這些數(shù)據(jù),我們根據(jù)它的shardkey計算出2、3是需要插入到SET1的;1和4是需要插入到SET2的。我們將這些需要插入的數(shù)據(jù)進(jìn)行拆分以后,再構(gòu)建對應(yīng)的Insert語句分別發(fā)送給SET1和SET2進(jìn)行執(zhí)行。如果一條更新語句沒有帶shardkey,例如這里的Delete語句,這個時候我們就需要將這條更新操作廣播給所有的SET執(zhí)行。如果一條更新語句更新了多個SET,我們就會使用分布式事務(wù)來保證這個更新操作的原子性。
3.3.2 復(fù)雜更新操作
而對于一個比較復(fù)雜的更新操作,例如這個例子里面的聯(lián)合更新SQL。對于這樣一個復(fù)雜的SQL,SQL引擎會將它拆分成兩個部分:一部分是Select,一部分是對應(yīng)的更新部分。對于這個語句來說就是一條Update。而Select的話,它主要的目的是提取出那些需要被更新行的主鍵,以及我們需要設(shè)置的新值。獲取這兩個關(guān)鍵信息以后,我們會構(gòu)建對應(yīng)的更新操作,因為這個例子里面就是一條Update語句——在這個語句里面我們指定了需要被更新行的主鍵,同時我們也指定了需要被更新的列和它的新值;然后將這條Update語句發(fā)送給對應(yīng)的SET執(zhí)行。對于這條Select語句的話,我們就看它的類型,看它是否shardkey相等——如果shardkey相等的話,我們就將由流式處理模型先處理;如果不相等,我們就用通用處理模型先處理。
3.3.3 更新操作中的分布式事務(wù)
前面我們多次提到分布式事務(wù),在前面的課程中,我的同事也對分布式事務(wù)做了一些講解。這里我們再簡要回顧一下。SQL引擎分布式事務(wù)所使用的算法是兩階段提交,對應(yīng)用來說其實不需要關(guān)心具體的實現(xiàn)細(xì)節(jié),對應(yīng)用來說就是簡單的Commit——當(dāng)業(yè)務(wù)發(fā)送Commit到SQL引擎的時候,SQL引擎就會向所有的參與者發(fā)送prepare,接下來每個收到prepare的主機將這個事務(wù)的狀態(tài)設(shè)置成prepare狀態(tài),并將這個事務(wù)所產(chǎn)生的binlog全部發(fā)送給備機。備機收到以后就會再向主返回一個應(yīng)答,主再向SQL引擎返回一個應(yīng)答,SQL引擎收到所有參與者的應(yīng)答之后就會做一個決策。當(dāng)所有的參與者都應(yīng)答成功,它就會做一個提交的決策,并將這個決策寫入到分布式的事務(wù)日志中。一旦寫成功的話,我們就可以直接將這個事務(wù)提交;而當(dāng)發(fā)生任何一種故障,我們都可以根據(jù)這個全局的分布式事務(wù)日志進(jìn)行提交或者回滾。
3.3.4 更新操作自增列優(yōu)化原理
使用數(shù)據(jù)庫的過程中,我們經(jīng)常會使用自增列。SQL引擎也提供了這個功能,當(dāng)應(yīng)用創(chuàng)建表的時候,如果指定了一個自增列,SQL引擎就向一個全局的元數(shù)據(jù)表中插入一個記錄,這個記錄包含了shardkey的名稱,以及下一個可以使用的值。當(dāng)應(yīng)用后續(xù)再發(fā)送INSERT語句過來的時候,SQL引擎就會對記錄中這個值進(jìn)行加1,然后再用原來的值來填充這條INSERT語句,再將這條填充后的Insert語句進(jìn)行拆分并發(fā)送到對應(yīng)的SET執(zhí)行。如果對每一條Insert語句我們都去訪問這一條記錄,這一條記錄必然會成為一個熱點,繼而對整個系統(tǒng)的穩(wěn)定性和性能都會產(chǎn)生很大的影響。所以SQL引擎丟棄了自增的屬性,只保留了它全區(qū)唯一的屬性。SQL引擎為了做到這一點,在每次獲取自增列值的時候會一次性獲取多個,然后再緩存到本地。當(dāng)應(yīng)用發(fā)送Insert 語句過來,就直接從本地的cache中拿出一個值來填充這個Insert ,避免熱點的產(chǎn)生,從而也提升了Insert語句的性能。當(dāng)然,如果用戶非得要使用遞增值,SQL引擎還提供序列功能,通過序列來滿足單調(diào)遞增的屬性。
4. TDQL-SQL引擎查詢最佳實踐
前面我們介紹了SQL引擎是如何處理各類查詢的,接下來是我們的最佳實踐。在這一章節(jié)我們會介紹選擇廣播表的基本原則;SQL引擎提供的基本命令等,通過這些命令我們可以看到一條SQL具體的執(zhí)行細(xì)節(jié)。
4.1 廣播表
使用廣播表可以讓一條SQL使用流式處理的方式進(jìn)行處理,流式處理的性能往往是比較高的。那么什么樣的一個表適合作為廣播表呢?
對于廣播表,SQL引擎需要將這個表全量備份到所有的SET上,這會產(chǎn)生空間上的消耗;同時對廣播表的每一次更新,都需要使用分布式事務(wù),從而保證這個更新操作的一致性。因此,我們優(yōu)先要選取的表,會希望它的數(shù)據(jù)量比較小,來減少對空間的占用;同時我們希望它的更新頻率比較低,這樣能夠降低對更新操作性能的影響。我們使用廣播表主要是為了優(yōu)化連接查詢,所以說我們選擇的廣播表本身,它應(yīng)該經(jīng)常被訪問,當(dāng)滿足這兩個條件,我們就可以建議用戶將這樣的一個表設(shè)置成廣播表。
4.2 explain信息解讀
在使用MySQL的時候,我們經(jīng)常使用的命令就是explain,通過explain我們可以看到查詢的執(zhí)行計劃,看到這條SQL是否使用了某個索引,或者說多表連接的順序是否符合預(yù)期,SQL引擎也提供了這樣一個功能。剛才我們說過一條select語句的執(zhí)行方式有兩種:一種是流式處理模型,一種是通用處理模型。對流式處理模型來說,SQL引擎會拆分成兩部分,一個是SET執(zhí)行部分和SQL引擎執(zhí)行部分。如果SQL引擎執(zhí)行的邏輯比較簡單,這個時候explain所產(chǎn)生的結(jié)果就是SET上的一個實際執(zhí)行計劃,例如對這條SQL來說,其實SQL引擎并沒有做什么比較大的計算,所以它產(chǎn)生的結(jié)果其實是MySQL上的執(zhí)行計劃。SQL引擎還會追加一些額外的信息,例如可以看到提示用戶這條SQL是發(fā)給哪個SET的,實際發(fā)給SET的是怎樣的一條SQL。
如果SQL引擎對SQL在進(jìn)行流式處理的過程中進(jìn)行了比較復(fù)雜的聚合,例如——對這條SQL來說,SQL引擎需要計算出count(distinct ?b),這個時候explain產(chǎn)生的結(jié)果就跟前面不一樣,explain會告訴用戶DB上執(zhí)行的是什么樣的查詢,SQL引擎進(jìn)行怎樣的聚合。這個例子里面,我們可以看到DB上執(zhí)行的是掃描查詢,并且下推了distinct,在DB部分進(jìn)行了初步去重。如果想要知道下推部分的執(zhí)行計劃,我們還提供了透傳命令——通過透傳命令我們可以看到這條SQL在每個SET上的具體執(zhí)行計劃。這個例子里面,我們可以看到,為了執(zhí)行這個計劃,每個SET使用的臨時表,并且進(jìn)行了一個排序。
對于使用通用處理模型處理的查詢,explain展示的信息又會不一樣。前面我們說過在通用處理模型下,我們需要對表進(jìn)行加載,因此explain輸出的結(jié)果就是告訴我們,SQL引擎到底使用了什么條件去加載數(shù)據(jù)。對于推導(dǎo)出來條件,explain會添加一個前綴/*esteimated*/加以提示。如果子查詢可以直接下推,explain同樣也會展示出來。在展示怎么樣加載數(shù)據(jù)和怎么樣下推查詢之后,explain會展示最終SQL引擎要執(zhí)行的查詢。
4.3 trace:展示SQL各個階段的耗時
當(dāng)然光知道一條SQL是怎么執(zhí)行,有時候還是不夠的的。因為影響一條SQL的執(zhí)行效率可能還跟當(dāng)前的環(huán)境、負(fù)載有關(guān)系。所以我們想知道一條SQL在實際執(zhí)行過程中各個階段的時耗,SQL引擎就提供了這樣一個強大的功能——trace。當(dāng)你在SQL的前面加一個trace前綴的時候,SQL引擎會執(zhí)行這條查詢并記錄在執(zhí)行過程中各個階段的時耗。例如在這個例子里面,這條SQL是以使用通用處理模型進(jìn)行處理的,所以在展示的結(jié)果里面,我們可以清晰地看到,加載T1時,從SET1和SET2加載數(shù)據(jù)的時耗,以及SET1和SET2返回的數(shù)據(jù)量。通過這些信息,我們就可以看到在數(shù)據(jù)加載過程中的具體細(xì)節(jié)。如果時耗比較大的話,我們就再去翻看前面的EXPLAIN信息,看看它的下推是否正常,下推之后是否能夠使用到索引。通過這些信息我們就可以清楚的看到一條SQL在各個階段所占用的時間,結(jié)合前面介紹的的explain命令,就可以很方便地定位到性能上的瓶頸了。
4.4 show processlist
另外我們在MySQL中經(jīng)常使用的一個命令還有show ?processlist,這個命令可以看到當(dāng)前性能中正在運行的一些查詢,SQL引擎也同樣提供了這個命令。通過這條命令可以看到系統(tǒng)中當(dāng)前正在運行的查詢,通過觀察查詢執(zhí)行時從后端DB加載的數(shù)據(jù)量,以及時耗,我們可以方便地定位到這個系統(tǒng)中哪條查詢是資源的消耗大戶。show processlist輸出中還有一個關(guān)鍵的字段,extra,因為SQL引擎會對用戶發(fā)送過來的SQL進(jìn)行拆解,實際發(fā)送給DB的SQL跟原生的SQL已經(jīng)不太一樣,extra會展示這些被拆分以后得到的SQL及其實際執(zhí)行的時間。這樣我們就可以將DB上的SQL和當(dāng)前正在運行的業(yè)務(wù)SQL結(jié)合起來,如果我們發(fā)現(xiàn)DB上被某條SQL占用了大部分資源,我們就可以通過這些信息將其與一條業(yè)務(wù)的SQL進(jìn)行一個關(guān)聯(lián)。
當(dāng)然,如果是DB出現(xiàn)慢查詢的時候,你剛好在電腦旁邊的話,就可以用剛剛說的show ?pracesslist進(jìn)行查看。如果你不在電腦旁邊,當(dāng)回到電腦旁邊的時候,這個高峰期已經(jīng)過去了,那怎么辦?SQL引擎會將應(yīng)用發(fā)過來的SQL,以及拆分過的SQL記錄到日志中。通過日志分析工具,例如這個例子,可以看到當(dāng)時SQL引擎對這條SQL的拆分,以及每條被拆分的SQL在哪個時間點發(fā)送到了哪一個SET,同時還展示每條SQL的影響行數(shù)及返回的數(shù)據(jù)量。通過日志分析工具,我們可以還原出現(xiàn)問題的時候當(dāng)時的現(xiàn)場,從而進(jìn)一步對業(yè)務(wù)的SQL進(jìn)行分析和優(yōu)化。
好了,今天我的分享就到這里了。謝謝大家!
Q&AQ:SQL引擎是自己開發(fā)的還是用的MySQL的引擎?
A:SQL引擎是我們自己開發(fā)的。
Q:如果做數(shù)據(jù)庫從Oracle換成TDSQL,對應(yīng)用來說需要的變更很大嗎?
A:TDSQL與Oracle在語法上會有一些差異,我們的一些客戶也是從Oracle遷移到TDSQL來的,實踐證明,只需要一點點改造,將部分Oracle 的SQL替換成與之相對應(yīng)的TDSQL語法即可。
Q:如果業(yè)務(wù)單表數(shù)量小的情況是不是不推薦使用這種分庫分表的方式?請問隨著業(yè)務(wù)的發(fā)展,當(dāng)數(shù)量擴展到多少時,數(shù)據(jù)庫推薦分庫分表?
A:數(shù)據(jù)量比較小的時候自然不需要使用分庫分表。當(dāng)數(shù)據(jù)量在可預(yù)期的時間范圍內(nèi),它可能會超過單機能夠承受的范圍,比如說當(dāng)可能有上T的數(shù)據(jù)時,就需要考慮分庫分表了。另外,當(dāng)一臺機器的計算能力滿足不了業(yè)務(wù)的話,也可以使用分庫分表,SQL引擎通過將計算進(jìn)行下推,使得計算能力隨著節(jié)點數(shù)而線性增長。所以說我們當(dāng)出現(xiàn)容量或者計算能力上單機無法滿足的情況時,我們就可以使用分庫分表。
Q:這個SQL引擎類似于Oracle 優(yōu)化器或sharding?開發(fā)者不用過度關(guān)注吧?
A:你用SQL引擎的話,使用TDSQL可以像使用單機MySQL一樣使用SQL引擎,一般來說你是不需要過度關(guān)注的。
Q:參數(shù)在每個SET上執(zhí)行的時候也是走事務(wù)嗎?不然每個SET的原子性沒法保證吧?
A:是的,通過SQL引擎開啟一個事務(wù)的時候,SQL引擎也會在每個SET上開啟相應(yīng)的子事務(wù)。
Q:分布式事務(wù)是基于MySQL原生的兩階段提交,不是基于BASE,兩階段提交性能會不會很差?
A:分布式事務(wù)的話,我們是基于原生的MySQL所提供的XA功能,但是在真正上線之前,其實我們對它做了很大的一個改造,就是說其實跟開源差別比較大。在實際使用過程中,兩階段提交會有一些性能影響,但是性能影響不是特別大,基本上影響可能在20%左右。
Q:Squence的實現(xiàn)只是保證遞增,不保證連續(xù)性嗎?如果可以實現(xiàn)單調(diào)遞增,具體的實現(xiàn)邏輯是怎么樣的?
A:Squence的話,是可以保證連遞增的,每次獲取新值都需要訪問元數(shù)據(jù)表。
Q:開發(fā)者應(yīng)該需要關(guān)注TDSQL的語法嗎?比如說建表的時候需要指定shardkey之類的。
A:前面的課程已經(jīng)講解過。如果建表的時候沒有指定shardkey——TDSQL會自己選擇一個shardkey,但是該功能默認(rèn)是關(guān)閉的。為什么呢?如果你沒有參與到選擇shardkey的過程,TDSQL沒法保證這個shardkey的選擇是最合適的,因為TDSQL不知道業(yè)務(wù)邏輯是怎么訪問這張表的。
推薦閱讀:
丁奇:深入解讀騰訊云國產(chǎn)數(shù)據(jù)庫技術(shù)演進(jìn)歷程
騰訊分布式數(shù)據(jù)庫TDSQL金融級能力的架構(gòu)原理解讀
騰訊數(shù)據(jù)庫RTO<30s,RPO=0高可用方案首次全景揭秘
億級流量場景下的平滑擴容:TDSQL的水平擴容方案實踐
總結(jié)
以上是生活随笔為你收集整理的流量洪峰成为常态,腾讯数据库如何高性能支撑海量SQL查询?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你一定听过这些不太标准的技术圈发音...
- 下一篇: 高可用架构设计之道,实战案例直面流量洪峰