中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)站推廣規(guī)劃百度問(wèn)答入口

網(wǎng)站推廣規(guī)劃,百度問(wèn)答入口,wordpress中點(diǎn)擊圖片_圖片顯示出來(lái)后的底色,阿里云小程序開發(fā)導(dǎo)讀 在數(shù)據(jù)庫(kù)系統(tǒng)中,查詢優(yōu)化器是數(shù)據(jù)庫(kù)管理系統(tǒng)的核心組成部分,負(fù)責(zé)將用戶的 SQL 查詢轉(zhuǎn)化為高效的執(zhí)行計(jì)劃,因而會(huì)直接影響用戶體感的性能與穩(wěn)定性。優(yōu)化器的設(shè)計(jì)與實(shí)現(xiàn)過(guò)程充滿挑戰(zhàn),有人比喻稱這是數(shù)據(jù)庫(kù)技術(shù)要持續(xù)攀登的珠穆…

導(dǎo)讀

在數(shù)據(jù)庫(kù)系統(tǒng)中,查詢優(yōu)化器是數(shù)據(jù)庫(kù)管理系統(tǒng)的核心組成部分,負(fù)責(zé)將用戶的 SQL 查詢轉(zhuǎn)化為高效的執(zhí)行計(jì)劃,因而會(huì)直接影響用戶體感的性能與穩(wěn)定性。優(yōu)化器的設(shè)計(jì)與實(shí)現(xiàn)過(guò)程充滿挑戰(zhàn),有人比喻稱這是數(shù)據(jù)庫(kù)技術(shù)要持續(xù)攀登的珠穆朗瑪峰,永遠(yuǎn)沒(méi)有最優(yōu)的止境。在一般的數(shù)據(jù)庫(kù)系統(tǒng)中,查詢優(yōu)化涉及復(fù)雜的算法和數(shù)據(jù)結(jié)構(gòu),需要綜合考慮多種因素,如數(shù)據(jù)分布、索引選擇、連接順序等,這些因素直接影響查詢的性能和資源利用率。

優(yōu)化器在 HTAP(Hybrid Transactional and Analytical Processing)系統(tǒng)難點(diǎn)尤為顯著?;旌闲拖到y(tǒng)要求優(yōu)化器在不同查詢模式下均能保持高效性,由于 TiDB 的 AP 和 TP 分屬不同的存儲(chǔ)和計(jì)算引擎,在構(gòu)造計(jì)劃時(shí)候還需要平衡不同計(jì)算引擎的計(jì)劃構(gòu)建,由于事務(wù)查詢與分析查詢?cè)谛再|(zhì)上的顯著差異,優(yōu)化器需在兩者之間實(shí)現(xiàn)合理的平衡,確保在高并發(fā)環(huán)境下的性能穩(wěn)定。

此外,優(yōu)化器的性能穩(wěn)定性也至關(guān)重要。設(shè)計(jì)不當(dāng)?shù)膬?yōu)化器可能導(dǎo)致查詢執(zhí)行時(shí)間紊亂,查詢計(jì)劃調(diào)變,計(jì)劃收斂不可控,甚至在負(fù)載變化時(shí)產(chǎn)生顯著的性能瓶頸。因此,從原理上深入理解優(yōu)化器的設(shè)計(jì)理念與實(shí)現(xiàn)細(xì)節(jié),對(duì)開發(fā)者優(yōu)化系統(tǒng)性能具有重要意義。

本系列文章將系統(tǒng)地探討優(yōu)化器的原理、設(shè)計(jì)思路及其在 TiDB HTAP 中的具體應(yīng)用,旨在為讀者提供全面的知識(shí)體系,以加深對(duì)優(yōu)化器重要性與復(fù)雜性的理解。希望通過(guò)整體剖面深入的分析,為數(shù)據(jù)庫(kù)性能優(yōu)化提供有價(jià)值的見解。

SQL 執(zhí)行流

現(xiàn)代數(shù)據(jù)庫(kù)數(shù)據(jù)處理過(guò)程跟以前數(shù)據(jù)庫(kù)運(yùn)維人員寫 loop 腳本來(lái)檢索文件并沒(méi)有差異,只是研發(fā)人員將這個(gè)過(guò)程標(biāo)準(zhǔn)化,用 SQL 語(yǔ)言來(lái)描述業(yè)務(wù)輸入,用數(shù)據(jù)庫(kù)來(lái)處理邏輯分析和執(zhí)行,人力資源更多的傾向于數(shù)據(jù)庫(kù)調(diào)優(yōu)和運(yùn)維,整體效率也更加高效,數(shù)據(jù)處理規(guī)模也愈發(fā)宏大。數(shù)據(jù)庫(kù)技術(shù)在過(guò)去幾十年不斷的演進(jìn)發(fā)展,逐步演化出單機(jī) / 多機(jī) / 中間件 / map-reduce / 分布式 / nosql / vector 等多種模式,優(yōu)化器也在其中不斷優(yōu)化迭代,出現(xiàn)了啟發(fā)式 / 代價(jià)模型 / volcano / cascades 等類型, 最終成就了現(xiàn)在優(yōu)化器的百花齊放。

對(duì)應(yīng)傳統(tǒng)的 SQL 執(zhí)行流,TiDB 的整個(gè) SQL 執(zhí)行可以分為以下三個(gè)部分,從語(yǔ)言層面來(lái)看:分別為 SQL 語(yǔ)言解釋器,IR 優(yōu)化器(我們可以將 LogicalPlan 所代表的 SQL 呈現(xiàn)稱為 IR)以及對(duì)應(yīng)的物理計(jì)劃執(zhí)行器。只不過(guò)除了一些謂詞下推、列裁剪等邏輯上的優(yōu)化,SQL 的優(yōu)化器還致力于尋找最優(yōu)執(zhí)行路徑組合。畢竟 SQL 語(yǔ)言本身的解釋器不需要直接翻譯成機(jī)器碼,而是動(dòng)態(tài)輸出其它高級(jí)語(yǔ)言描述的物理計(jì)劃,這部分物理計(jì)劃在下發(fā)到其它執(zhí)行引擎時(shí)是以定義好的 protobuf 的形式傳輸?shù)?#xff0c;這部分序列化呈現(xiàn)還是需要被 TiKV 和 TiFlash 接收,翻譯成內(nèi)部的具體的執(zhí)行流來(lái)執(zhí)行。由于 TiDB 有 3 套執(zhí)行引擎,所以物理執(zhí)行器類型有 3 種,在優(yōu)化器生成計(jì)劃的時(shí)候,需要考慮到物理 Engine Type 的計(jì)劃區(qū)別。而各個(gè)具體物理執(zhí)行算子的邏輯是既定寫到在各個(gè)執(zhí)行引擎里的,這部分更底層的 C++/Rust/Go 語(yǔ)言級(jí)的解釋和優(yōu)化在編譯各個(gè)組件時(shí)候就已經(jīng)優(yōu)化好了。

C++/Rust/Go 語(yǔ)言級(jí)的解釋和優(yōu)化

這樣來(lái)說(shuō),數(shù)據(jù)庫(kù)其實(shí)就是一個(gè) SQL 語(yǔ)言的解釋器和優(yōu)化器,輸出是物理計(jì)劃本身??紤]到執(zhí)行引擎和存儲(chǔ)引擎可能是存算分離結(jié)構(gòu),這部分的自由度可以多種多樣,只要在序列化協(xié)議層規(guī)范好 DAG(有向無(wú)環(huán)圖) 的解釋執(zhí)行應(yīng)該就可以了。因此也致使涌現(xiàn)了一些像 Calcite 這種插件式 SQL 語(yǔ)言的解釋器和優(yōu)化器(沒(méi)有存儲(chǔ)和計(jì)算);同時(shí)也涌現(xiàn)了些像 Velox 這種只規(guī)范了物理計(jì)劃呈現(xiàn)接口統(tǒng)一執(zhí)行引擎框架(沒(méi)有解釋器和優(yōu)化);這些都是 SQL 語(yǔ)言前后端組件在走向標(biāo)準(zhǔn)化,統(tǒng)一化中演進(jìn)出來(lái)的形式。而從語(yǔ)言前后端的關(guān)系來(lái)分,查詢優(yōu)化器屬于 SQL 語(yǔ)言編譯器的后端范。

SQL 優(yōu)化器

SQL 優(yōu)化器的主要輸出是物理計(jì)劃,負(fù)責(zé)對(duì)給定 SQL 語(yǔ)句的擇優(yōu)執(zhí)行計(jì)劃。接下來(lái)的章節(jié)將主要聚焦在優(yōu)化過(guò)程本身。以下圖示是 TiDB 內(nèi)部 SQL 執(zhí)行流程圖:

SQL 執(zhí)行流程圖

  • MySQL Protocol:協(xié)議層
  • Parser:SQL 語(yǔ)法解析器,產(chǎn)生 AST
  • 由 AST 做映射判斷是否有 PlanCache(v8.4 之前僅支持 session 級(jí)別的 plan cache,之后支持了 instance 級(jí)別的 plan cache),如果可以直接將 cached 的 physical plan refill 參數(shù)之后即可使用。
  • Build Plan:常規(guī) AST 結(jié)構(gòu)到邏輯計(jì)劃的構(gòu)建過(guò)程
  • Logical Plan 有兩條路可以走,其中 Stats 貫穿其中提供估算:
    • 點(diǎn)查計(jì)劃的的 Fast Plan,當(dāng)判斷 SQL 為點(diǎn)查(where 條件為主鍵字段或唯一性索引字段返回 1 行),則將 SQL 發(fā)送給 TiDB Executor 去 TiKV 獲取數(shù)據(jù),同時(shí)還要完成表達(dá)式簡(jiǎn)化,子查詢簡(jiǎn)化處理等。
    • 常規(guī)計(jì)劃的 Logical Optimize 和 Physical Optimize:
  • Logical Optimize:邏輯優(yōu)化是對(duì)關(guān)系代數(shù)表達(dá)式進(jìn)行啟發(fā)式的常量傳播,列裁剪,謂詞下推,outer join 消除等邏輯;
  • Physical Optimize:物理優(yōu)化是參考統(tǒng)計(jì)信息對(duì) Logical Optimized Plan 的結(jié)果進(jìn)行基于 cost 的計(jì)算和判斷,擇優(yōu) cost 最低的物理執(zhí)行計(jì)劃,包括表 join 方式,索引掃描方式,表的掃描方式,算子是否下推到 TiKV 等(物理存儲(chǔ)引擎的計(jì)劃分層也是在物理優(yōu)化階段做的)。
  • 物理計(jì)劃根據(jù)存儲(chǔ)引擎分發(fā):Root Task(TiDB 端),Cop Task(TiKV/TiFlash 端),MPP Task(TiFlash 端)

SQL 算子

上述有提到 SQL 語(yǔ)言是結(jié)構(gòu)化半描述語(yǔ)言,它描述的信息索取的邏輯方式,邏輯操作方式由 SQL 中不同的邏輯操作子句體現(xiàn),這些子句有一定的邏輯操作順序,轉(zhuǎn)譯到 LogicalPlan Tree 中就是不同的邏輯操作符號(hào) LogicalPlan 的樹形層次順序。接下來(lái)看下 SQL 以 Query Block 為構(gòu)建單元的其中子句構(gòu)建順序,及其映射到邏輯和物理計(jì)劃的算子的對(duì)應(yīng)關(guān)系。

在語(yǔ)言層面來(lái)看,Logical Plan 其實(shí)是 SQL 語(yǔ)言編譯器的 IR (Internal Representation)呈現(xiàn),無(wú)論你是什么樣的 SQL Dialect 都可以轉(zhuǎn)化到統(tǒng)一的 IR 呈現(xiàn),具體在優(yōu)化器后期要翻譯成什么樣的物理計(jì)劃的呈現(xiàn),取決執(zhí)行器框架里對(duì)邏輯算子轉(zhuǎn)譯支持的豐富程度。

 7 個(gè)基本子句

這個(gè) 7 個(gè)基本子句構(gòu)成了一個(gè) SQL 中一個(gè) Query Block 的構(gòu)建單元,如果任何一個(gè)子句中穿插引入子查詢,那將遞歸深入進(jìn)去到一個(gè)新的 Query Block 構(gòu)建流程中,這個(gè)子 Query Block 構(gòu)建完成之后會(huì)在邏輯計(jì)劃中以一個(gè)子樹的形式存在,這個(gè)子樹的根節(jié)點(diǎn)是一個(gè) LogicalApply 算子,其左孩子是被關(guān)聯(lián)子查詢的邏輯計(jì)劃,右孩子是關(guān)聯(lián)查詢子 Query Block 的邏輯計(jì)劃,回溯到根 Query Block 時(shí),再以當(dāng)前新的 Apply 算子為基,基于根 Query Block 上次構(gòu)建的時(shí)停滯的子句單元繼續(xù)依次構(gòu)建。

上述列表描述的就是常規(guī) SQL 子句到 TiDB 內(nèi)部 AST,甚至到 Explain 中顯示的邏輯算子映射。由于邏輯優(yōu)化的存儲(chǔ),Explain 中顯示的計(jì)劃是優(yōu)化后的結(jié)果,其根原始 SQL 邏輯展示形式有一定的區(qū)別。有一些特定的算子,其在 SQL 語(yǔ)句的呈現(xiàn)中并不一定要具有自己獨(dú)立的子句描述,比如 Aggregation Plan,SQL 文本中任何子句中如果任何地方出現(xiàn)了 Agg 函數(shù),我們都需要當(dāng)前查詢 block 上下文在 Projection 之前的進(jìn)行數(shù)據(jù)的聚合運(yùn)算,不然如果在 Projection 之后,Agg 函數(shù)所需要的聚合參數(shù)可能已經(jīng)被 Project 掉了,Agg 聚合操作就無(wú)法做了,當(dāng)然不同的數(shù)據(jù)庫(kù)實(shí)現(xiàn)有些微差別,本質(zhì)上都是在數(shù)據(jù) Filter 之后進(jìn)行有效數(shù)據(jù)的聚合計(jì)算,才是符合正確的 Agg SQL 語(yǔ)義的。

有些 Logical Plan 并沒(méi)有實(shí)際對(duì)應(yīng)的 SQL 子句,比如 Logical Apply,其來(lái)源是在構(gòu)建關(guān)聯(lián)子查詢的時(shí)候,關(guān)聯(lián)計(jì)劃子樹和被關(guān)聯(lián)計(jì)劃子樹之間需要進(jìn)行 Apply 模式的運(yùn)行,即被關(guān)聯(lián)子樹每次傳入一行給關(guān)聯(lián)子樹執(zhí)行,返回結(jié)果后繼續(xù)下一行的傳入。這種來(lái)源于 SQL 語(yǔ)義本身的 Apply 執(zhí)行模式會(huì)被直接 build 為 LogicalApply,由于 Apply 執(zhí)行方式的并不算特別高效,后續(xù)在邏輯計(jì)劃階段,會(huì)有一些解關(guān)聯(lián)邏輯變換將 LogicalApply 轉(zhuǎn)化為 LogicalJoin 執(zhí)行。

有些 Logical Plan 是一些下推操作和本身的保序性質(zhì)聯(lián)合導(dǎo)致的,比如 LogicalTopN,其如果解耦開來(lái)就是兩個(gè)疊加的 Logical Plan(LogicalLimit + LogicalSort),在數(shù)據(jù)庫(kù)火山執(zhí)行流中,更多的算子意味著更多的數(shù)據(jù)流動(dòng)和運(yùn)算,因?yàn)閮煞N屬性結(jié)合的 LogicalTopN 可以更好的在一個(gè)算子內(nèi)完成一致的動(dòng)作,甚至也可以讓排序算法本身顯得更為高效,比如歸并排序甚至都不用排完就可以直接返回?cái)?shù)據(jù)并終止執(zhí)行了。

有些 Logical Plan 本身可能是來(lái)自于 SQL 的有些子句修飾符,比如 With Rollup,Rollup 是一個(gè)多維度聚合的 Group By 字句的修飾符號(hào),其用法大致為 Agg(x) from t Group By a,b with rollup,后續(xù)的 Agg(x) 的聚合結(jié)果需要在數(shù)據(jù)分組 {a,b} 中輸出一次,數(shù)據(jù)分組 {a} 中輸出一次,數(shù)據(jù)分組 {} 也就是全域?yàn)橐粋€(gè) Group 中輸出一次。實(shí)際應(yīng)用場(chǎng)景大致為銀行報(bào)表業(yè)務(wù)的按照Group By 年,月,日的多維度聚合結(jié)果展示 。TiDB 目前采用數(shù)據(jù)復(fù)制操作符 LogicalExpand 來(lái)復(fù)制底層數(shù)據(jù),不同的數(shù)據(jù)副本按照不同的分組層次來(lái)進(jìn)行分組,然后輸入給上次聚合函數(shù)進(jìn)行計(jì)算。

Index Join

Join 圖示

上述 Join 圖示中,我們標(biāo)注了 Build 和 Probe 字樣,該字樣如果標(biāo)注在 Join 下方的兩個(gè)孩子算子中,則表示的是該 Join 的左右孩子在 Join 執(zhí)行過(guò)程中充當(dāng)?shù)尿?qū)動(dòng)和被驅(qū)動(dòng)角色,驅(qū)動(dòng)端一般指的是 Hash Join 中用來(lái)構(gòu)建哈希表的一側(cè),Index Join / Apply/Nest Loop Join 中先行直接讀到內(nèi)存中的相對(duì)行數(shù)較小的一側(cè),相應(yīng)的 Probe 端則是基于哈希表查詢的一側(cè),后續(xù)基于已讀行走索引,甚至是 Nest Loop 中的被驅(qū)動(dòng)一側(cè);如果是標(biāo)注在 IndexlookUp / IndexMerge 下方兩側(cè)孩子中,則驅(qū)動(dòng)端表示現(xiàn)行基于索引直接讀的一側(cè),相應(yīng)的 Probe 端表示的在則是后續(xù)基于索引讀到的 rowid/handle 來(lái)回表的一側(cè)。

如果預(yù)計(jì)需要連接的行數(shù)較少,推薦使用 Index Join 算法。這個(gè)算法與 MySQL 主要使用 Join 算法類似。Index Join 其實(shí)是 Apply 模式的一個(gè)高級(jí)形式,其特別在 Probe 端可以走索引并且可以贊批,所以才從 Apply 算子 row by row 的執(zhí)行模式中脫離出來(lái),自行優(yōu)化。Index Join 的 Probe 端不需要等待 Build 完全結(jié)束,其 Build 端在按到第一批數(shù)據(jù)之后,就可以直接交與 Probe 側(cè)去驅(qū)動(dòng)索引。

  • 使用 Hint INL_JOIN 進(jìn)行 Index Join 操作,該操作是流式的,Build 的數(shù)據(jù)在動(dòng)態(tài)的給 Probe 端是走 Index 查詢。

  • 使用 Hint INL_HASH_JOIN 在外表執(zhí)行返回的部分構(gòu)上建 Hash Table,該算法區(qū)別 Hash Join 的全局哈希,而是基于流式數(shù)據(jù)的局部哈希,在特定的場(chǎng)景中可以減少內(nèi)存壓力。

Index Join 算法的性能受以下系統(tǒng)變量影響:

  • tidb_index_join_batch_size(默認(rèn)值:25000)index lookup join 操作的 batch 大小。
  • tidb_index_lookup_join_concurrency(默認(rèn)值:Unset = DefExecutorConcurrency (5) )- 可以并發(fā)執(zhí)行的 index lookup 任務(wù)數(shù)。

使用建議:

Index Join Porbe 端的并行是 Batch Size 為單位的并行,Probe Wokers 每次消費(fèi)一個(gè) Batch Size 的回表任務(wù)來(lái)加速 Probe 過(guò)程。每當(dāng)一個(gè)并行任務(wù)的驅(qū)動(dòng) task 數(shù)量 < concurrency 時(shí),說(shuō)明沒(méi)有充分利用 Probe 并發(fā),應(yīng)該適當(dāng)調(diào)小當(dāng)驅(qū)動(dòng)表的 Batch Size,增加 task 數(shù)量,以便為了更高效地觸發(fā) Probe 的并行。

對(duì)比 explain analyze 結(jié)果中的 concurrency 和 task

對(duì)比 explain analyze 結(jié)果中的 concurrency 和 task

若 concurrency < task,則并發(fā)度最高是 concurrency 的值 (probe 并發(fā)已經(jīng)被充分利用)

若 concurrency >= task,則并發(fā)度最高是 task 的值 (可以適當(dāng)減少 session batch size,增加 task 數(shù)量,提高 probe 并發(fā)利用)

Task = 驅(qū)動(dòng)表行數(shù) / tidb_index_join_batch_size

Index Join Prior

  • 當(dāng)在 Build 端數(shù)據(jù)行較小時(shí)候,Index Join 和 Hash join 都是可以
  • 當(dāng) Probe 端數(shù)據(jù)量很大的情況,僅 Index Join Build 端行較小時(shí)候可以使用已讀行來(lái)驅(qū)動(dòng) Probe 讀,避免 Probe 側(cè)行數(shù)的爆炸,不然其回表 Task 數(shù)量將很不可控,造成 TiKV 的請(qǐng)求堆積。
  • 當(dāng)執(zhí)行環(huán)境對(duì)內(nèi)存有限制,Index Join 的流式執(zhí)行可以更好的緩解這種壓力,但其運(yùn)行速度可能會(huì)慢于其它 Join 算法

Hash Join

在 Hash Join 操作中,TiDB 首先讀取 Build 端的數(shù)據(jù)并將其構(gòu)建在 Hash Table 中,然后再讀取 Probe 端的數(shù)據(jù),使用 Probe 端的數(shù)據(jù)來(lái)探查 Hash Table 以獲得所需行。與 Index Join 算法相比,Hash Join 的 Probe 端是需要嚴(yán)格等待 Build 端拿到所有數(shù)據(jù)構(gòu)建完 Hash Table 后才允許 Probe 端執(zhí)行的。Hash Join 要消耗更多內(nèi)存,但如果需要左右兩端連接的行數(shù)都很多,運(yùn)行速度會(huì)比 Index Join 快。TiDB 中的 Hash Join 算子是多線程的,并且可以并發(fā)執(zhí)行。

tidb> explain analyze select * from t1, t2 where t1.a = t2.a and t1.a=1;
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
| id                           | estRows  | actRows    | task      | access object | execution info                                                                                                                                                                                                                                    | operator info                  | memory   | disk    |
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
| HashJoin_8                   | 9663.68  | 1073741824 | root      |               | time:22.7s, loops:1048577, RU:38.960331, build_hash_table:{total:29.3ms, fetch:28ms, build:1.31ms}, probe:{concurrency:5, total:1m51.7s, max:22.8s, probe:1m47.3s, fetch and wait:4.41s}                                                          | CARTESIAN inner join           | 1.14 MB  | 0 Bytes |
| ├─TableReader_15(Build)      | 98.30    | 32768      | root      |               | time:28.3ms, loops:33, cop_task: {num: 8, max: 12.7ms, min: 304.4μs, avg: 3.57ms, p95: 12.7ms, tot_proc: 25ms, copr_cache_hit_ratio: 0.00, build_task_duration: 3μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:8, total_time:28.5ms}}   | data:Selection_14              | 399.7 KB | N/A     |
| │ └─Selection_14             | 98.30    | 32768      | cop[tikv] |               | tikv_task:{proc max:12.7ms, min:280.2μs, avg: 3.52ms, p80:7.09ms, p95:12.7ms, iters:0, tasks:8}, time_detail: {total_process_time: 25ms}                                                                                                          | eq(1, test.t2.a)               | N/A      | N/A     |
| │   └─TableFullScan_13       | 98304.00 | 98304      | cop[tikv] | table:t2      | tikv_task:{proc max:12.7ms, min:280.2μs, avg: 3.52ms, p80:7.09ms, p95:12.7ms, iters:0, tasks:8}                                                                                                                                                   | keep order:false, stats:pseudo | N/A      | N/A     |
| └─TableReader_12(Probe)      | 98.30    | 32768      | root      |               | time:6.3ms, loops:33, cop_task: {num: 8, max: 8.93ms, min: 329.4μs, avg: 2.89ms, p95: 8.93ms, tot_proc: 19ms, copr_cache_hit_ratio: 0.00, build_task_duration: 22.1μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:8, total_time:23ms}}   | data:Selection_11              | 399.7 KB | N/A     |
|   └─Selection_11             | 98.30    | 32768      | cop[tikv] |               | tikv_task:{proc max:8.83ms, min:304.7μs, avg: 2.83ms, p80:6.28ms, p95:8.83ms, iters:0, tasks:8}, time_detail: {total_process_time: 19ms}                                                                                                          | eq(test.t1.a, 1)               | N/A      | N/A     |
|     └─TableFullScan_10       | 98304.00 | 98304      | cop[tikv] | table:t1      | tikv_task:{proc max:8.83ms, min:304.7μs, avg: 2.83ms, p80:6.28ms, p95:8.83ms, iters:0, tasks:8}                                                                                                                                                   | keep order:false, stats:pseudo | N/A      | N/A     |
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
7 rows in set (22.83 sec)

Hash Join 會(huì)比較消耗內(nèi)存,可以通過(guò) tidb_mem_quota_query 對(duì) SQL 消耗內(nèi)存進(jìn)行控制,內(nèi)存使用超過(guò)了 tidb_mem_quota_query 規(guī)定的值(默認(rèn)為 1GB),且 oom-use-tmp-storage 的值為 true (默認(rèn)為 true),那么 TiDB 會(huì)嘗試使用臨時(shí)存儲(chǔ),該文件目錄由配置參數(shù) tmp-storage-path 控制,在磁盤上創(chuàng)建 Hash Join 的 Build 端。

Hash Join 算法的性能受以下系統(tǒng)變量影響:

  • tidb_mem_quota_query(默認(rèn)值:1GB) 如果某條查詢的內(nèi)存消耗超出了配額,TiDB 會(huì)嘗試將 Hash Join 的 Build 端移到磁盤上以節(jié)省內(nèi)存。
  • tidb_hash_join_concurrency(默認(rèn)值:5)可以并發(fā)執(zhí)行的 Hash Join 任務(wù)數(shù)量。

使用建議:

tidb_hash_join_concurrency 參數(shù)控制并發(fā)執(zhí)行的 Hash Join 任務(wù)數(shù)量,tidb_hash_join_concurrency 的所有任務(wù)計(jì)算過(guò)程當(dāng)中所申請(qǐng)的內(nèi)存使用量總和不超過(guò) tidb_mem_quota_query,否則超過(guò)部分會(huì)移到磁盤從而導(dǎo)致性能降低。

mysql> explain analyze select * from t1,t2 where t1.a=t2.a;
+--------------------------------+----------+-----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+---------+
| id                             | estRows  | actRows   | task      | access object | execution info                                                                                                                                                                                                                                        | operator info                                    | memory   | disk    |
+--------------------------------+----------+-----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+---------+
| Projection_9                   | 12487.50 | 440401920 | root      |               | time:7.9s, loops:430081, RU:30.108091, Concurrency:5                                                                                                                                                                                                  | test.t1.a, test.t1.b, test.t2.a, test.t2.b       | 379.7 KB | N/A     |
| └─HashJoin_25                  | 12487.50 | 440401920 | root      |               | time:7.71s, loops:430081, build_hash_table:{total:4.84ms, fetch:3.62ms, build:1.22ms}, probe:{concurrency:5, total:38.5s, max:7.95s, probe:37s, fetch and wait:1.55s}                                                                                 | inner join, equal:[eq(test.t2.a, test.t1.a)]     | 956.3 KB | 0 Bytes |
|   ├─TableReader_31(Build)      | 9990.00  | 26880     | root      |               | time:3.85ms, loops:28, cop_task: {num: 8, max: 1.54ms, min: 104.6μs, avg: 553.4μs, p95: 1.54ms, tot_proc: 2ms, copr_cache_hit_ratio: 0.00, build_task_duration: 4.83μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:8, total_time:4.38ms}}    | data:Selection_30                                | 303.7 KB | N/A     |
|   │ └─Selection_30             | 9990.00  | 26880     | cop[tikv] |               | tikv_task:{proc max:1.48ms, min:95.3μs, avg: 523.5μs, p80:1.2ms, p95:1.48ms, iters:0, tasks:8}, time_detail: {total_process_time: 2ms}                                                                                                                | not(isnull(test.t2.a))                           | N/A      | N/A     |
|   │   └─TableFullScan_29       | 10000.00 | 26880     | cop[tikv] | table:t2      | tikv_task:{proc max:1.48ms, min:95.3μs, avg: 523.5μs, p80:1.2ms, p95:1.48ms, iters:0, tasks:8}                                                                                                                                                        | keep order:false, stats:pseudo                   | N/A      | N/A     |
|   └─TableReader_28(Probe)      | 49102.85 | 49152     | root      |               | time:1.95ms, loops:49, cop_task: {num: 9, max: 3.37ms, min: 93.1μs, avg: 1ms, p95: 3.37ms, tot_proc: 6ms, copr_cache_hit_ratio: 0.00, build_task_duration: 1.27μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:9, total_time:8.96ms}}         | data:Selection_27                                | 532.7 KB | N/A     |
|     └─Selection_27             | 49102.85 | 49152     | cop[tikv] |               | tikv_task:{proc max:3.27ms, min:74.8μs, avg: 965.3μs, p80:2.32ms, p95:3.27ms, iters:0, tasks:9}, time_detail: {total_process_time: 6ms}                                                                                                               | not(isnull(test.t1.a))                           | N/A      | N/A     |
|       └─TableFullScan_26       | 49152.00 | 49152     | cop[tikv] | table:t1      | tikv_task:{proc max:3.27ms, min:74.8μs, avg: 965.3μs, p80:2.32ms, p95:3.27ms, iters:0, tasks:9}                                                                                                                                                       | keep order:false, stats:partial[a:unInitialized] | N/A      | N/A     |
+--------------------------------+----------+-----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+---------+
8 rows in set (7.95 sec)

當(dāng) disk>0 則需要優(yōu)化 tidb_mem_quota_query 參數(shù)。

Hash Join Prior

  • 如果 Join 左右孩子表行數(shù)都很大,這時(shí)走 Apply 很慢,走 Index Join(如果可以走的話)cop task 數(shù)量很多,所以 Hash Join 可能比較好
  • 在 Join 一側(cè)孩子表行比較小的時(shí)候,其比較適合用與 hash 表的構(gòu)建,如果另外一側(cè)沒(méi)有索引,那么 hash join 是最好的選擇,考慮到 hash join 自身的并法,在行數(shù)差距不是特別大的情況下,hash join 都還是比較好用的。

Merge Join

Merge Join 是一種特殊的 Join 算法。當(dāng)兩個(gè)關(guān)聯(lián)表要 Join 的字段需要按排好的順序讀取時(shí),適用 Merge Join 算法。由于 Build 端和 Probe 端的數(shù)據(jù)都會(huì)有序讀取,并進(jìn)行歸并輸出,Join 操作是流式向上的,在底層數(shù)據(jù)很大時(shí)候,內(nèi)存又有限制時(shí)候,Merge Join 非常適合對(duì) latency 要求不高或者不敏感的場(chǎng)景。由于歸并限制了并發(fā)的存在,所以 Merge Join 在執(zhí)行速度上有所欠缺。

 Merge Join

使用建議:

Merge Join Prior

  • Merge join 可以直接的接受孩子節(jié)點(diǎn)傳遞過(guò)來(lái)的有序數(shù)據(jù)源,可以避免 Join 過(guò)程中的對(duì)齊操作(其對(duì) hash join 來(lái)說(shuō)就是哈希映射,對(duì)于 Index Join 來(lái)說(shuō)就是索引行索取,而 Merge Join 本身的對(duì)其是按行在歸并排序中的,是最簡(jiǎn)單的一種對(duì)齊)
  • 考慮到歸并操作的特殊性質(zhì),其并不能并發(fā)操作,所以其對(duì)內(nèi)存非常友好,通??梢杂迷趦?nèi)存敏感且對(duì)于延遲不敏感的環(huán)境中。

Null Aware Join

有些時(shí)候 Join 連接詞來(lái)自于集合謂詞,而不是普通等值 EQ 的時(shí)候,就會(huì)產(chǎn)生 Null Aware 的需求??紤]到下面的例子:

explain select (a, b) not in (select a, b from naaj_B) from naaj_A;

這里的 Not In 是集合謂詞(需要感知空集,NULL 性質(zhì)),雖然可以轉(zhuǎn)化為 Anti Semi Join,但是 (a, b) = (a, b) 在 v7.0 之前是默認(rèn)不作為 Join 條件的??梢钥吹接?jì)劃中有笛卡爾積的關(guān)鍵詞

有笛卡爾積的關(guān)鍵詞

這種計(jì)劃,在之前 v7.0 之前最好是通過(guò) SQL 改寫的方式避免集合謂詞的使用,比如可以改成 Exists 的等價(jià)查詢

explain select * from naaj_A where NOT exists (select a, b from naaj_B where 
naaj_B.a = naaj_A.a and naaj_B.b = naaj_A.b) ;

 Exists 的等價(jià)查詢

在 v7.0 之后,TiDB 引入了 Null Aware join 的性質(zhì),可以將集合謂詞來(lái)作為 Join Key,在 runtime 過(guò)程中去感知集合謂詞所需要的空集和 Null 值屬性,以便加速整個(gè)計(jì)算過(guò)程,避免笛卡爾積的使用。

將集合謂詞來(lái)作為 Join Key

注意:

現(xiàn)在集合謂詞 IN 條件導(dǎo)致的 Join 沒(méi)有 join key,IN 自身?xiàng)l件會(huì)在 other condition 里面,導(dǎo)致 Join 本身是個(gè)笛卡爾積的存在,這種 case 暫時(shí)還沒(méi)有用 Null Aware 優(yōu)化,需要手動(dòng)改寫 Exists 子查詢。

Apply Mode Prior

LogicalApply 是一個(gè)非常原始的 row by row 驅(qū)動(dòng)的內(nèi)外表執(zhí)行方式,同時(shí)存在于子查詢的構(gòu)建初期,后續(xù)結(jié)果解關(guān)聯(lián)優(yōu)化之后,大部分都會(huì)變成 join 來(lái)執(zhí)行。如果 Probe 側(cè)有合適的索引且沒(méi)有一些異常算子的阻隔,那么可以規(guī)劃到 IndexJoin 來(lái)執(zhí)行;在沒(méi)有其他幫助下也能規(guī)劃一個(gè) HashJoin 來(lái)執(zhí)行。但是有一種時(shí)候,row by row 的 Apply 執(zhí)行方式反而是最好的,看接下來(lái)這個(gè)例子 issues/37789 ( https://github.com/pingcap/tidb/issues/37789 )。

create table t1(a int, b int);
create table t2(a int, b int, index ia(a));
explain select a > (select sum(b) from t2 where a = t1.b) from t1;+------------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------+
| id                                 | estRows  | task      | access object | operator info                                                                             |
+------------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------+
| Projection_10                      | 10000.00 | root      |               | gt(cast(test.t1.a, decimal(20,0) BINARY), Column#10)->Column#11                           |
| └─HashJoin_11                      | 10000.00 | root      |               | left outer join, equal:[eq(test.t1.b, test.t2.a)]                                         |
|   ├─HashAgg_22(Build)              | 7992.00  | root      |               | group by:test.t2.a, funcs:sum(Column#13)->Column#10, funcs:firstrow(test.t2.a)->test.t2.a |
|   │ └─TableReader_23               | 7992.00  | root      |               | data:HashAgg_15                                                                           |
|   │   └─HashAgg_15                 | 7992.00  | cop[tikv] |               | group by:test.t2.a, funcs:sum(test.t2.b)->Column#13                                       |
|   │     └─Selection_21             | 9990.00  | cop[tikv] |               | not(isnull(test.t2.a))                                                                    |
|   │       └─TableFullScan_20       | 10000.00 | cop[tikv] | table:t2      | keep order:false, stats:pseudo                                                            |
|   └─TableReader_14(Probe)          | 10000.00 | root      |               | data:TableFullScan_13                                                                     |
|     └─TableFullScan_13             | 10000.00 | cop[tikv] | table:t1      | keep order:false, stats:pseudo                                                            |
+------------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------+

可以看到在常規(guī)規(guī)劃下,選擇的是 Hash Join + Table Full Scan(t1, t2)。如果 t1 的表非常小,t1.b = a 就可以過(guò)濾很多數(shù)據(jù),用 Apply 算子就可以讓 t1 表作為驅(qū)動(dòng)表,然后帶動(dòng) t2 側(cè),利用 ia 索引來(lái)范圍查詢數(shù)據(jù)就會(huì)快很多。有人說(shuō)這不就是 IndexJoin,沒(méi)有錯(cuò),IndexJoin 就可以看作是一種 Apply 但是在構(gòu)建 IndexJoin 的時(shí)候,v7.0.0 之前的 TiDB 還是有很多局限,比如這里的 IndexJoin 的 Probe 端就變成了一個(gè)具有 Agg 的子樹,這個(gè)在老版本是中無(wú)法構(gòu)建 IndexJoin 的,這個(gè)時(shí)候就需要用 /+ no_decorrelate() / 來(lái)方 HashJoin 保留成原始的 Apply 模式,讓 Probe 側(cè)的復(fù)雜子樹也能走索引了。

在新版本的 v8.2.0 TiDB 中,已經(jīng)加強(qiáng)了 TiDB 的能力 pull/51354 ( https://github.com/pingcap/tidb/pull/51354 ),一些簡(jiǎn)單 case 需要配合使用 tidb_enable_inl_join_inner_multi_pattern=1,才能正確走到 IndexJoin 中,這個(gè) case 中在老的版本中就只能通過(guò) /+ no_decorrelate() / 來(lái)保留 raw Apply mode 來(lái)走內(nèi)側(cè)索引驅(qū)動(dòng)了。

mysql> explain select a > (select /*+ no_decorrelate() */  sum(b) from t2 where a = t1.b) from t1;
+------------------------------------------+-----------+-----------+-----------------------+------------------------------------------------------------------------------+
| id                                       | estRows   | task      | access object         | operator info                                                                |
+------------------------------------------+-----------+-----------+-----------------------+------------------------------------------------------------------------------+
| Projection_10                            | 10000.00  | root      |                       | gt(cast(test.t1.a, decimal(10,0) BINARY), Column#10)->Column#11              |
| └─Apply_12                               | 10000.00  | root      |                       | CARTESIAN left outer join                                                    |
|   ├─TableReader_14(Build)                | 10000.00  | root      |                       | data:TableFullScan_13                                                        |
|   │ └─TableFullScan_13                   | 10000.00  | cop[tikv] | table:t1              | keep order:false, stats:pseudo                                               |
|   └─MaxOneRow_15(Probe)                  | 10000.00  | root      |                       |                                                                              |
|     └─StreamAgg_20                       | 10000.00  | root      |                       | funcs:sum(Column#19)->Column#10                                              |
|       └─Projection_45                    | 100000.00 | root      |                       | cast(test.t2.b, decimal(10,0) BINARY)->Column#19                             |
|         └─IndexLookUp_44                 | 100000.00 | root      |                       |                                                                              |
|           ├─IndexRangeScan_42(Build)     | 100000.00 | cop[tikv] | table:t2, index:ia(a) | range: decided by [eq(test.t2.a, test.t1.b)], keep order:false, stats:pseudo |
|           └─TableRowIDScan_43(Probe)     | 100000.00 | cop[tikv] | table:t2              | keep order:false, stats:pseudo                                               |
+------------------------------------------+-----------+-----------+-----------------------+------------------------------------------------------------------------------+
10 rows in set (0.00 sec)

SQL 算子最佳實(shí)踐與使用建議

Hash Join 是默認(rèn)的 Join 算法,在下層算子提供任何有序?qū)傩缘臅r(shí)候,能夠執(zhí)行。此外 Hash Join 的左右孩子可以利用索引來(lái)加速掃描過(guò)程,但是 Hash Join 的性質(zhì)沒(méi)有變化,依舊需要在 Build 表上構(gòu)建出 Hash Table,并以此為基礎(chǔ)上來(lái) Probe 另外一端,Probe 端的開始時(shí)間嚴(yán)格在 Build Hash Table 之后。考慮其左右孩子都可以并發(fā),大多數(shù)情況下都有不錯(cuò)的性能。

考慮到 Hash 表構(gòu)建需要把所有元組都緩存在內(nèi)存,這種阻塞方式在表很大時(shí)候,會(huì)有很大的 OOM 風(fēng)險(xiǎn),因此可以考慮流式 Join 的方式(這里流式指的是,Join 數(shù)據(jù)流動(dòng)沒(méi)有全量等待,左右孩子數(shù)據(jù)流動(dòng)也是 row/batch 為單位向上流動(dòng)的)。流式 Join 在如何找到左右孩子合適的匹配行算法上分為:

  • 通過(guò) Index 查找 Probe 行:Index Join(Probe 可以并行),及部分 Apply Mode(Apply 的 Probe 側(cè)走索引回表)。
  • Build 側(cè)和 Probe 側(cè)同時(shí)保證 Join Key 有序性質(zhì):Merge Join。

流式 Join 的方式,主要是根據(jù)孩子特定的物理屬性(有序或者有索引)來(lái)快速查找相應(yīng)的匹配行。大多數(shù)情況,流式 Join 的方式并發(fā)都比較有限(Index Join 只有 Probe 并發(fā)),或者沒(méi)有并發(fā)(Merge Join),其對(duì)內(nèi)存友好,在有序保證的前提下可以考慮。在大小表場(chǎng)景中,并且大表在 Join Key 有索引的情況下,Index Join 能夠很好的避免全表讀,利用 row_id/handle scan 的回表能力;考慮到回表的離散性,如果每次大表 Join Key 的回表都比較分散,在一定的情況下需要獨(dú)立的發(fā)送 COP RPC 請(qǐng)求,這種為了很多單一回表導(dǎo)致的大量 COP 會(huì)導(dǎo)致 TiKV 側(cè)請(qǐng)求堆積且疲于處理,最終會(huì)導(dǎo)致延遲增加。最終權(quán)衡選擇時(shí)應(yīng)該特別關(guān)注:

  • Join 基表的行數(shù),Hash Join 有無(wú)處理慢的情況,可以調(diào)并發(fā)加速否。
  • Hash Join 的內(nèi)存壓力,有無(wú) OOM 風(fēng)險(xiǎn),是否需要降低并發(fā),或者選擇流式 Join。
  • Join 左右表的各自物理屬性,有沒(méi)有合適流式 Join 可以選。
  • Index Join 的 Probe 側(cè)并發(fā),如果 Index Join Build 側(cè)重復(fù)值很多,可以考慮 Index Hash Join。
  • 關(guān)于 Index Join 注意實(shí)際 Cop 請(qǐng)求數(shù)量,以及 TiKV Cop 請(qǐng)求處理數(shù)量和延遲。

邏輯執(zhí)行計(jì)劃

在上述文章提到,由 SQL 文本自帶的語(yǔ)義層次翻譯過(guò)來(lái)的邏輯計(jì)劃在執(zhí)行上并不是最優(yōu)的,通常一些顯而易見的常量傳播,列裁剪,謂詞下推,apply 消除等操作并沒(méi)有人為寫好,這些因?yàn)?SQL 在設(shè)計(jì)之初的邏輯描述是泛化的,僅是符合人為邏輯思考的,而不是為了貼近方便計(jì)算而生的。這種計(jì)算貼合的優(yōu)化應(yīng)該就是優(yōu)化器本身的責(zé)任所在了,就也是邏輯優(yōu)化應(yīng)該需要做到的事了。

當(dāng)前 TiDB 使用的常規(guī) Logical Rules

上述非詳盡的羅列了一些當(dāng)前 TiDB 使用的常規(guī) Logical Rules,對(duì)其中的邏輯解釋也只是使用了部分比較特征化的例子,更多詳細(xì)的 rules 和更多復(fù)雜的應(yīng)用場(chǎng)景可以參考:pkg/planner/core ( https://github.com/pingcap/tidb/tree/master/pkg/planner/core )。需要注意當(dāng)前 TiDB 認(rèn)為邏輯計(jì)劃 rule 總是 always good and substituted 的,其不會(huì)保留優(yōu)化前的副本計(jì)劃。

謂詞下推

謂詞下推

謂詞下推屬于邏輯優(yōu)化階段,當(dāng)一個(gè) SQL 需要訪問(wèn)的數(shù)據(jù)分散在多個(gè) TiKV 節(jié)點(diǎn)時(shí),SQL 將多個(gè) TiKV 節(jié)點(diǎn)的數(shù)據(jù)統(tǒng)一拉到 TiDB Server 節(jié)點(diǎn)過(guò)濾和匯聚的性能是遠(yuǎn)低于在 TiKV 節(jié)點(diǎn)層完成各自節(jié)點(diǎn)數(shù)據(jù)的過(guò)濾然后再將結(jié)果傳到計(jì)算層 TiDB Server 聚合,該操作就叫謂詞下推。該特殊的好處在于 SQL 數(shù)據(jù)訪問(wèn)的過(guò)程中,TiKV 與 TiDB Server 的數(shù)據(jù)傳輸量降低,且也降低了 TiDB Server 側(cè)數(shù)據(jù)緩存的資源開銷。

謂詞下推屬于邏輯優(yōu)化階段

執(zhí)行計(jì)劃信息解讀

對(duì)用戶來(lái)說(shuō)優(yōu)化器的優(yōu)化過(guò)程是比較黑盒的,唯一的結(jié)果觀測(cè)窗口就是 Explain Info,雖然是一種比較后置的手段,但是其中也結(jié)合經(jīng)驗(yàn)?zāi)芘袛嘤?jì)劃的優(yōu)化的合理性。一旦觀測(cè)到計(jì)劃結(jié)果不對(duì)之后,可以使用 optimizer tracer 功能跟蹤整個(gè)邏輯和物理優(yōu)化過(guò)程,找到問(wèn)題的癥結(jié)。

解讀 Explain 的計(jì)劃結(jié)果

tidb> explain analyze select * from t1, t2 where t1.a = t2.a and t1.a=1;
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
| id                           | estRows  | actRows    | task      | access object | execution info                                                                                                                                                                                                                                    | operator info                  | memory   | disk    |
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
| HashJoin_8                   | 9663.68  | 1073741824 | root      |               | time:22.7s, loops:1048577, RU:38.960331, build_hash_table:{total:29.3ms, fetch:28ms, build:1.31ms}, probe:{concurrency:5, total:1m51.7s, max:22.8s, probe:1m47.3s, fetch and wait:4.41s}                                                          | CARTESIAN inner join           | 1.14 MB  | 0 Bytes |
| ├─TableReader_15(Build)      | 98.30    | 32768      | root      |               | time:28.3ms, loops:33, cop_task: {num: 8, max: 12.7ms, min: 304.4μs, avg: 3.57ms, p95: 12.7ms, tot_proc: 25ms, copr_cache_hit_ratio: 0.00, build_task_duration: 3μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:8, total_time:28.5ms}}   | data:Selection_14              | 399.7 KB | N/A     |
| │ └─Selection_14             | 98.30    | 32768      | cop[tikv] |               | tikv_task:{proc max:12.7ms, min:280.2μs, avg: 3.52ms, p80:7.09ms, p95:12.7ms, iters:0, tasks:8}, time_detail: {total_process_time: 25ms}                                                                                                          | eq(1, test.t2.a)               | N/A      | N/A     |
| │   └─TableFullScan_13       | 98304.00 | 98304      | cop[tikv] | table:t2      | tikv_task:{proc max:12.7ms, min:280.2μs, avg: 3.52ms, p80:7.09ms, p95:12.7ms, iters:0, tasks:8}                                                                                                                                                   | keep order:false, stats:pseudo | N/A      | N/A     |
| └─TableReader_12(Probe)      | 98.30    | 32768      | root      |               | time:6.3ms, loops:33, cop_task: {num: 8, max: 8.93ms, min: 329.4μs, avg: 2.89ms, p95: 8.93ms, tot_proc: 19ms, copr_cache_hit_ratio: 0.00, build_task_duration: 22.1μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:8, total_time:23ms}}   | data:Selection_11              | 399.7 KB | N/A     |
|   └─Selection_11             | 98.30    | 32768      | cop[tikv] |               | tikv_task:{proc max:8.83ms, min:304.7μs, avg: 2.83ms, p80:6.28ms, p95:8.83ms, iters:0, tasks:8}, time_detail: {total_process_time: 19ms}                                                                                                          | eq(test.t1.a, 1)               | N/A      | N/A     |
|     └─TableFullScan_10       | 98304.00 | 98304      | cop[tikv] | table:t1      | tikv_task:{proc max:8.83ms, min:304.7μs, avg: 2.83ms, p80:6.28ms, p95:8.83ms, iters:0, tasks:8}                                                                                                                                                   | keep order:false, stats:pseudo | N/A      | N/A     |
+------------------------------+----------+------------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------+----------+---------+
7 rows in set (22.83 sec)

Explain 的返回結(jié)果包含以下字段:

  • id 為算子名(對(duì)應(yīng)物理計(jì)劃中所示),或執(zhí)行 SQL 語(yǔ)句需要執(zhí)行的子任務(wù)。

    • TableFullScan:全表掃描。

    • TableRangeScan:帶有范圍的表數(shù)據(jù)掃描。

    • TableRowIDScan:根據(jù)上層傳遞下來(lái)的 RowID 掃描表數(shù)據(jù)。時(shí)常在索引讀操作后檢索符合條件的行。

    • IndexFullScan:另一種“全表掃描”,掃的是索引數(shù)據(jù),不是表數(shù)據(jù)。

    • IndexRangeScan:帶有范圍的索引數(shù)據(jù)掃描操作。

    • TableReader:將 TiKV 上底層掃表算子 TableFullScan 或 TableRangeScan 得到的數(shù)據(jù)進(jìn)行匯總。

    • IndexReader:將 TiKV 上底層掃表算子 IndexFullScan 或 IndexRangeScan 得到的數(shù)據(jù)進(jìn)行匯總。

      IndexLookUp:先匯總 Build 端 TiKV 掃描上來(lái)的 RowID,再去 Probe 端上根據(jù)這些 RowID 精確地讀取 TiKV 上的數(shù)據(jù)。Build 端是IndexFullScan 或 IndexRangeScan 類型的算子,Probe 端是 TableRowIDScan 類型的算子。

    • IndexMerge:和 IndexLookupReader 類似,可以看做是它的擴(kuò)展,可以同時(shí)讀取多個(gè)索引的數(shù)據(jù),有多個(gè) Build 端,一個(gè) Probe 端。執(zhí)行過(guò)程也很類似,先匯總所有 Build 端 TiKV 掃描上來(lái)的 RowID,再去 Probe 端上根據(jù)這些 RowID 精確地讀取 TiKV 上的數(shù)據(jù)。Build 端是 IndexFullScan 或 IndexRangeScan 類型的算子,Probe 端是 TableRowIDScan 類型的算子。

    • estRows 為顯示 TiDB 預(yù)計(jì)會(huì)處理的行數(shù)。該預(yù)估數(shù)可能基于字典信息(例如訪問(wèn)方法基于主鍵或唯一鍵),或基于 CMSketch 或直方圖等統(tǒng)計(jì)信息估算而來(lái)。

  • task 顯示算子在執(zhí)行語(yǔ)句時(shí)的所在位置。

    • 目前 TiDB 的計(jì)算任務(wù)分為三種不同的 task:cop task,root task 和 mpp task。cop task 是指使用 TiKV 中的 Coprocessor 執(zhí)行的計(jì)算任務(wù),root task 是指在 TiDB 中執(zhí)行的計(jì)算任務(wù),而 mpp task 旨在 tiflash 上以 mpp 模式執(zhí)行的計(jì)劃。
    • SQL 優(yōu)化的目標(biāo)之一是將計(jì)算盡可能下推到 TiKV / TiFlash 中執(zhí)行。TiKV 和 TiFlash 能支持大部分 SQL 內(nèi)建函數(shù)(包括聚合函數(shù)和標(biāo)量函數(shù))、SQL Limit 操作、索引掃描和表掃描。
  • access-object 顯示被訪問(wèn)的表、分區(qū)和索引。在有組合索引的情況下,該字段顯示的索引可能為部分索引,此時(shí)可以通過(guò)所 operator-info 構(gòu)建 range 使用的前綴參數(shù)來(lái)判斷,很有參考意義。當(dāng)被訪問(wèn)表示分區(qū)表時(shí)候,access-object 可以判斷出 partition pruning 是否正確的裁剪了其他分區(qū)。

  • execution info 顯示 explain analyze 特有的執(zhí)行細(xì)節(jié)信息。

    • time:為算子執(zhí)行的時(shí)間,單位 ms
    • loops
  • operator-info 顯示訪問(wèn)表、分區(qū)和索引的其他信息。

    • 其實(shí)就是各種表達(dá)式的 CNF 或者 DNF 組合
    • 可以判斷有哪些 agg 函數(shù),由 funcs: 標(biāo)識(shí)
    • 可以判斷有哪些 group by 列,由 group by: 標(biāo)識(shí)
    • 可以判斷 join 類型:比如 inner join,left outer
    • 可以判斷是不是笛卡爾積(有沒(méi)有 join key)CARTESIAN
    • 可以判斷有哪些 join key 條件,由 equal:[xxx] 標(biāo)識(shí)
    • 可以判斷有哪些后置的其他條件,other cond: 標(biāo)識(shí)
    • 如果使用了索引,可以查看索引 range 的構(gòu)造信息,使用了多少前綴列等。
    • keep order 屬性,可以判斷使用利用底層有序?qū)ο蟮挠行蛐?#xff0c;比索引 / pk 有序。
    • stats 屬性可判斷當(dāng)前這個(gè)計(jì)劃,底層數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象上的統(tǒng)計(jì)信息是不是 pesudo。
    • 常量直接顯示字面值
    • column 如果有自己的原始 name,即用 name 不然就是 column#id
    • 函數(shù)一般是標(biāo)識(shí)為:函數(shù)名(參數(shù),常量)比如:eq(column#2, 0),至于 column#2 的來(lái)源需要去下層算子溯源之后才知道來(lái)自什么樣的計(jì)算了。
    • 表達(dá)式的標(biāo)識(shí)
    • 對(duì) datasource 來(lái)說(shuō)
    • 對(duì) Join 算子來(lái)說(shuō)
    • 對(duì) Agg 算子來(lái)說(shuō)
    • 對(duì) Selection 算子來(lái)說(shuō)

解讀 Explain Analyze 的執(zhí)行結(jié)果

和 EXPLAIN 不同,EXPLAIN ANALYZE 會(huì)執(zhí)行對(duì)應(yīng)的 SQL 語(yǔ)句,記錄其運(yùn)行時(shí)信息,和執(zhí)行計(jì)劃一并返回出來(lái),可以視為 EXPLAIN 語(yǔ)句的擴(kuò)展。EXPLAIN ANALYZE 語(yǔ)句的返回結(jié)果中增加了 actRows, execution info,memory,disk 這幾列信息:

  • actRows

    • 當(dāng)前算子實(shí)際輸出的數(shù)據(jù)條數(shù)
  • execution info

    • time 顯示從進(jìn)入算子到離開算子的全部時(shí)間,包括所有子算子操作的全部執(zhí)行時(shí)間。loops 是當(dāng)前算子被父算子調(diào)用 Next 的次數(shù)。如果該算子被父算子多次 Next 調(diào)用,記作 loops,這個(gè) time 時(shí)間就是累積的時(shí)間。rows 是當(dāng)前算子返回的行的總數(shù)。此外對(duì)于不同的存儲(chǔ)引擎,其 cop_task, tikv_scan 的細(xì)節(jié)都會(huì)一一記錄
  • memory

    • 當(dāng)前算子占用內(nèi)存大小
  • disk

    • 當(dāng)前算子占用磁盤大小

一個(gè)例子:

mysql> explain analyze select * from t where a < 10;
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
| id                            | estRows | actRows | task      | access object           | execution info                                                         | operator info                                       | memory        | disk |
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
| IndexLookUp_10                | 9.00    | 9       | root      |                         | time:641.245μs, loops:2, rpc num: 1, rpc time:242.648μs, proc keys:0   |                                                     | 9.23046875 KB | N/A  |
| ├─IndexRangeScan_8(Build)     | 9.00    | 9       | cop[tikv] | table:t, index:idx_a(a) | time:142.94μs, loops:10,                                               | range:[-inf,10), keep order:false                   | N/A           | N/A  |
| └─TableRowIDScan_9(Probe)     | 9.00    | 9       | cop[tikv] | table:t                 | time:141.128μs, loops:10                                               | keep order:false                                    | N/A           | N/A  |
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
3 rows in set (0.00 sec)

從上述例子中可以看出,優(yōu)化器估算的 estRows 和實(shí)際執(zhí)行中統(tǒng)計(jì)得到的 actRows 幾乎是相等的,說(shuō)明優(yōu)化器估算的行數(shù)與實(shí)際行數(shù)的誤差很小。同時(shí) IndexLookUp_10 算子在實(shí)際執(zhí)行過(guò)程中使用了約 9 KB 的內(nèi)存,該 SQL 在執(zhí)行過(guò)程中,沒(méi)有觸發(fā)過(guò)任何算子的落盤操作。執(zhí)行總時(shí)間差不多 641 微秒,調(diào)用 Loops 為 2。

解讀算子的執(zhí)行順序

算子的結(jié)構(gòu)是樹狀的,但在查詢執(zhí)行過(guò)程中,并不嚴(yán)格要求子節(jié)點(diǎn)任務(wù)在父節(jié)點(diǎn)之前完成。TiDB 支持同一查詢內(nèi)的并行處理,即子節(jié)點(diǎn)“流入”父節(jié)點(diǎn)。父節(jié)點(diǎn)、子節(jié)點(diǎn)和同級(jí)節(jié)點(diǎn)可能并行執(zhí)行查詢的一部分。

TableReader_32 算子為 t1 表所對(duì)應(yīng)

上述示例中,TableReader_32 算子為 t1 表所對(duì)應(yīng),IndexLookUp_10 為t2 表所對(duì)應(yīng)。IndexJoin 是流式 Join,其 Build 側(cè)行總是流式的讀取,并同時(shí)作為索引行 Probe t2 表的數(shù)據(jù),可以看到在其下方是使用 IndexRangeScan_8 走 t1_id 索引來(lái)直接讀取數(shù)據(jù),其索引列 t1_id 是來(lái)自于 t1 表。利用驅(qū)動(dòng)側(cè)傳遞的 t1_id 在運(yùn)行時(shí)的動(dòng)態(tài)常量性質(zhì),我們可以構(gòu)建 t2 表上索引 t1_id 的范圍,隨后從 t2表利用 TableRowIdScan_9直接檢索這些行的 handle / rowid。

Build 總是先于 Probe 并且后續(xù)可能同時(shí)執(zhí)行,并且 Build 字樣總是出現(xiàn)在 Probe 前面。即如果一個(gè)算子有多個(gè)子節(jié)點(diǎn),子節(jié)點(diǎn) ID 后面有 Build 關(guān)鍵字的算子總是先于有 Probe 關(guān)鍵字的算子執(zhí)行。TiDB 在展現(xiàn)執(zhí)行計(jì)劃的時(shí)候,Build 端總是第一個(gè)出現(xiàn),接著才是 Probe 端。Probe 端的開始時(shí)間相較于 Build 中還是 Build 之后,跟算子的性質(zhì)有關(guān)系,比如上述提到的 Hash Join 和 Index Join。

解讀 Query Block 層次

執(zhí)行計(jì)劃對(duì)應(yīng) SQL 有時(shí)候會(huì)比較復(fù)雜,特別是當(dāng)涉及子查詢或者設(shè)計(jì)多表的時(shí)候,但是從優(yōu)化器的構(gòu)建思想來(lái)看,其實(shí)是分層的,每層可以看作是一個(gè)簡(jiǎn)單的 Query Block,每層的構(gòu)建路徑都是相同的,執(zhí)行計(jì)劃的展示也是每層構(gòu)建完之后層層堆疊起來(lái)的,而后才是經(jīng)過(guò)邏輯優(yōu)化,join reorder 等一系列變化來(lái)改變計(jì)劃樹的形態(tài)。

如果你想知道計(jì)劃哪里不對(duì),那么兩個(gè)東西首先你要有:

  • 一個(gè)是 plan build 完之后的樹是什么樣的,這個(gè)是原始來(lái)自于 Query Block 層次的堆疊的 Logical Plan 樹
  • 一個(gè)是后續(xù)要經(jīng)歷的 Logical Plan Rule 的列表,這個(gè)已經(jīng)在上述章節(jié)中介紹到了,目前 TiDB 的邏輯優(yōu)化空間不算特別大,也不算特別復(fù)雜。

心中有了這兩樣,在來(lái)對(duì)應(yīng)看 Explain 計(jì)劃就能舉一反三,迅速找到計(jì)劃異常點(diǎn):下面來(lái)個(gè)例子看下。

=drop table if exists t1;
create table t1(c1 int, c2 int, c3 int, key(c1), key(c2));
insert into t1 values(4, 4, 4), (5, 5, 5);
insert into t1 select * from t1; * N;drop table if exists t2;
create table t2(c1 int, c2 int, c3 int, key(c1), key(c2));
insert into t2 values(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5);tidb> explain select * from t1 where c1 < 2 or c2 < 2 and c3 < ANY(select count(1) from t2);
+----------------------------------+------------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id                               | estRows    | task      | access object          | operator info                                                                                                                                                                                             |
+----------------------------------+------------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| HashJoin_12                      | 3355443.20 | root      |                        | CARTESIAN inner join, other cond:or(lt(test.t1.c1, 2), and(and(lt(test.t1.c2, 2), or(lt(test.t1.c3, Column#10), if(ne(Column#11, 0), NULL, 0))), and(ne(Column#12, 0), if(isnull(test.t1.c3), NULL, 1)))) |
| ├─StreamAgg_18(Build)            | 1.00       | root      |                        | funcs:max(Column#9)->Column#10, funcs:sum(0)->Column#11, funcs:count(1)->Column#12                                                                                                                        |
| │ └─StreamAgg_38                 | 1.00       | root      |                        | funcs:count(Column#22)->Column#9                                                                                                                                                                          |
| │   └─IndexReader_39             | 1.00       | root      |                        | index:StreamAgg_22                                                                                                                                                                                        |
| │     └─StreamAgg_22             | 1.00       | cop[tikv] |                        | funcs:count(1)->Column#22                                                                                                                                                                                 |
| │       └─IndexFullScan_36       | 5.00       | cop[tikv] | table:t2, index:c1(c1) | keep order:false, stats:pseudo                                                                                                                                                                            |
| └─TableReader_16(Probe)          | 3355443.20 | root      |                        | data:Selection_15                                                                                                                                                                                         |
|   └─Selection_15                 | 3355443.20 | cop[tikv] |                        | or(lt(test.t1.c1, 2), and(lt(test.t1.c2, 2), if(isnull(test.t1.c3), NULL, 1)))                                                                                                                            |
|     └─TableFullScan_14           | 4194304.00 | cop[tikv] | table:t1               | keep order:false                                                                                                                                                                                          |
+----------------------------------+------------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
9 rows in set, 1 warning (0.00 sec)

這里我們有兩張表 t1 和 t2,一個(gè)不算復(fù)雜的 select 查詢帶了很多 where 條件,上述章節(jié)中有提到,sql 的撰寫層次跟 logical plan 的 tree 結(jié)構(gòu)層次基本是依依對(duì)應(yīng)的。這里按照算子章節(jié)的語(yǔ)法層級(jí)來(lái)拆解一下,有著這個(gè) SQL 是有子查詢的,對(duì)應(yīng)外部主查詢?yōu)橐粋€(gè) Query Block。也就是有兩層 Query Block,由外到內(nèi)我們依次命名為 Query Block1 和 Query Block2。

  • Query Block1

Query Block1

  • Query Block2

Query Block2

在兩個(gè) Query Block 都對(duì)解釋應(yīng)完了之后,將 Query Block1 中引用 Query Block2 的地方對(duì)應(yīng)替換過(guò)去就行,最終就形成了我們的 Explain 計(jì)劃。各個(gè)計(jì)劃的來(lái)源和 SQL Query Block 層次是一一對(duì)應(yīng)的。

SQL 執(zhí)行計(jì)劃舉例分析

一般 explain 只能看到邏輯計(jì)劃,在分析性能瓶頸中,還得看 explain analyze 的結(jié)果。explain 結(jié)果和 explain analyze 的結(jié)果的樹是一樣的,只不過(guò)后者加入了執(zhí)行的細(xì)節(jié),在執(zhí)行的細(xì)節(jié)中我們更容易找到時(shí)間瓶頸,并發(fā)瓶頸,內(nèi)存大頭。這里找到 execution info 列中耗時(shí)最長(zhǎng)的瓶頸地方,然后對(duì)應(yīng)分析 id 列中的邏輯算子是不是符合計(jì)劃預(yù)期。

tidb> explain analyze select  * from t1 where c1 < 2 or c2 < 2 and c3 < ANY(select count(1) from t2);
+----------------------------------+------------+---------+-----------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
| id                               | estRows    | actRows | task      | access object          | execution info                                                                                                                                                                                                                                                                                  | operator info                                                                                                                                                                                             | memory    | disk    |
+----------------------------------+------------+---------+-----------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
| HashJoin_12                      | 1677721.60 | 0       | root      |                        | time:4.74s, loops:1, RU:27860.254239, build_hash_table:{total:44.9ms, fetch:44.9ms, build:3.46μs}                                                                                                                                                                                               | CARTESIAN inner join, other cond:or(lt(test.t1.c1, 2), and(and(lt(test.t1.c2, 2), or(lt(test.t1.c3, Column#10), if(ne(Column#11, 0), NULL, 0))), and(ne(Column#12, 0), if(isnull(test.t1.c3), NULL, 1)))) | 59.2 KB   | 0 Bytes |
| ├─StreamAgg_18(Build)            | 1.00       | 1       | root      |                        | time:44.8ms, loops:2                                                                                                                                                                                                                                                                            | funcs:max(Column#9)->Column#10, funcs:sum(0)->Column#11, funcs:count(1)->Column#12                                                                                                                        | 460 Bytes | N/A     |
| │ └─StreamAgg_38                 | 1.00       | 1       | root      |                        | time:44.8ms, loops:2                                                                                                                                                                                                                                                                            | funcs:count(Column#22)->Column#9                                                                                                                                                                          | 8.25 KB   | N/A     |
| │   └─IndexReader_39             | 1.00       | 1       | root      |                        | time:44.8ms, loops:2, cop_task: {num: 1, max: 110.8μs, proc_keys: 0, copr_cache_hit_ratio: 0.00, build_task_duration: 1.29μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:1, total_time:98.5μs}}                                                                                        | index:StreamAgg_22                                                                                                                                                                                        | 204 Bytes | N/A     |
| │     └─StreamAgg_22             | 1.00       | 1       | cop[tikv] |                        | tikv_task:{time:84.3μs, loops:0}                                                                                                                                                                                                                                                                | funcs:count(1)->Column#22                                                                                                                                                                                 | N/A       | N/A     |
| │       └─IndexFullScan_36       | 5.00       | 5       | cop[tikv] | table:t2, index:c1(c1) | tikv_task:{time:84.3μs, loops:0}                                                                                                                                                                                                                                                                | keep order:false, stats:pseudo                                                                                                                                                                            | N/A       | N/A     |
| └─TableReader_16(Probe)          | 1677721.60 | 0       | root      |                        | time:4.74s, loops:1, cop_task: {num: 6, max: 1.1s, min: 33.9ms, avg: 496ms, p95: 1.1s, tot_proc: 2.94s, copr_cache_hit_ratio: 0.00, build_task_duration: 28.1μs, max_distsql_concurrency: 6}, rpc_info:{Cop:{num_rpc:55382, total_time:9.87s},ResolveLock:{num_rpc:55376, total_time:635.4ms}}  | data:Selection_15                                                                                                                                                                                         | 224 Bytes | N/A     |
|   └─Selection_15                 | 1677721.60 | 0       | cop[tikv] |                        | tikv_task:{proc max:1.09s, min:33.9ms, avg: 490.6ms, p80:839.3ms, p95:1.09s, iters:0, tasks:6}, time_detail: {total_process_time: 2.94s}                                                                                                                                                        | or(lt(test.t1.c1, 2), and(lt(test.t1.c2, 2), if(isnull(test.t1.c3), NULL, 1)))                                                                                                                            | N/A       | N/A     |
|     └─TableFullScan_14           | 2097152.00 | 4194304 | cop[tikv] | table:t1               | tikv_task:{proc max:1.09s, min:33.9ms, avg: 490.6ms, p80:839.3ms, p95:1.09s, iters:0, tasks:6}                                                                                                                                                                                                  | keep order:false                                                                                                                                                                                          | N/A       | N/A     |
+----------------------------------+------------+---------+-----------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
9 rows in set, 1 warning (4.75 sec)
  • 這里在大數(shù)據(jù)量我們可以明顯看到 TableReader_16 的延遲到了 5 秒左右。但是其實(shí) t1 表上是有相關(guān) {c1,c2,c3} 是索引的,可以看到 Selection_15 這個(gè) t1 的 selection 其實(shí)是下推了下來(lái),但是并沒(méi)有被用作索引的檢索條件,而且從執(zhí)行結(jié)果來(lái)看并沒(méi)有相關(guān)的有效數(shù)據(jù),僅僅是掃描 t1 花了這么多時(shí)間,那么能不能將 selection 條件全部用于 index 范圍查詢呢。
  • 當(dāng)前這里為了說(shuō)明問(wèn)題,我們一直是將 SET session tidb_enable_index_merge = OFF 的,這里直接打開開關(guān)之后就可以直接選到 IndexMerge 的計(jì)劃,在老舊的 TiDB 版本或者統(tǒng)計(jì)信息不準(zhǔn)確的 TiDB 實(shí)例中,觀測(cè)到上述問(wèn)題之后,可以先使用 Hint /*+ use_index_merge(t1) */ 指導(dǎo)優(yōu)化器選擇 IndexMerge 的物理實(shí)現(xiàn)??梢钥吹诫m然同樣是沒(méi)有掃有效的數(shù)據(jù),但是走三路 Merge 的 Index 索引路徑快樂(lè)很多,只要 0.1s 左右就可以執(zhí)行完畢。
tidb> explain analyze select /*+ use_index_merge(t1) */ * from t1 where c1 < 2 or c2 < 2 and c3 < ANY(select count(1) from t2);
+----------------------------------+------------+---------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
| id                               | estRows    | actRows | task      | access object          | execution info                                                                                                                                                                                                                                                                                                                                                                                | operator info                                                                                                                                                                                             | memory    | disk    |
+----------------------------------+------------+---------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
| HashJoin_12                      | 3355443.20 | 0       | root      |                        | time:87ms, loops:1, RU:68.567903, build_hash_table:{total:462.6μs, fetch:453.8μs, build:8.88μs}                                                                                                                                                                                                                                                                                               | CARTESIAN inner join, other cond:or(lt(test.t1.c1, 2), and(and(lt(test.t1.c2, 2), or(lt(test.t1.c3, Column#10), if(ne(Column#11, 0), NULL, 0))), and(ne(Column#12, 0), if(isnull(test.t1.c3), NULL, 1)))) | 60.7 KB   | 0 Bytes |
| ├─StreamAgg_23(Build)            | 1.00       | 1       | root      |                        | time:362μs, loops:2                                                                                                                                                                                                                                                                                                                                                                           | funcs:max(Column#9)->Column#10, funcs:sum(0)->Column#11, funcs:count(1)->Column#12                                                                                                                        | 460 Bytes | N/A     |
| │ └─StreamAgg_43                 | 1.00       | 1       | root      |                        | time:338.2μs, loops:2                                                                                                                                                                                                                                                                                                                                                                         | funcs:count(Column#25)->Column#9                                                                                                                                                                          | 8.25 KB   | N/A     |
| │   └─IndexReader_44             | 1.00       | 1       | root      |                        | time:331.1μs, loops:2, cop_task: {num: 1, max: 404.3μs, proc_keys: 0, copr_cache_hit_ratio: 0.00, build_task_duration: 30μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:1, total_time:373.3μs}}                                                                                                                                                                                      | index:StreamAgg_27                                                                                                                                                                                        | 204 Bytes | N/A     |
| │     └─StreamAgg_27             | 1.00       | 1       | cop[tikv] |                        | tikv_task:{time:323.8μs, loops:0}                                                                                                                                                                                                                                                                                                                                                             | funcs:count(1)->Column#25                                                                                                                                                                                 | N/A       | N/A     |
| │       └─IndexFullScan_41       | 5.00       | 5       | cop[tikv] | table:t2, index:c1(c1) | tikv_task:{time:323.8μs, loops:0}                                                                                                                                                                                                                                                                                                                                                             | keep order:false, stats:pseudo                                                                                                                                                                            | N/A       | N/A     |
| └─IndexMerge_21(Probe)           | 2684354.56 | 0       | root      |                        | time:86.8ms, loops:1                                                                                                                                                                                                                                                                                                                                                                          | type: union                                                                                                                                                                                               | 200 Bytes | N/A     |
|   ├─IndexRangeScan_17(Build)     | 0.00       | 0       | cop[tikv] | table:t1, index:c1(c1) | time:85.5ms, loops:1, cop_task: {num: 1, max: 85.5ms, proc_keys: 0, tot_proc: 85ms, copr_cache_hit_ratio: 0.00, build_task_duration: 219.3μs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:1, total_time:85.5ms}}, tikv_task:{time:85.4ms, loops:0}, time_detail: {total_process_time: 85ms}                                                                                           | range:[-inf,2), keep order:false                                                                                                                                                                          | N/A       | N/A     |
|   ├─IndexRangeScan_18(Build)     | 0.00       | 0       | cop[tikv] | table:t1, index:c2(c2) | time:62.2ms, loops:1, cop_task: {num: 2, max: 62.1ms, min: 53.8ms, avg: 57.9ms, p95: 62.1ms, tot_proc: 115ms, copr_cache_hit_ratio: 0.00, build_task_duration: 43.8μs, max_distsql_concurrency: 2}, rpc_info:{Cop:{num_rpc:2, total_time:115.8ms}}, tikv_task:{proc max:62.1ms, min:53.7ms, avg: 57.9ms, p80:62.1ms, p95:62.1ms, iters:0, tasks:2}, time_detail: {total_process_time: 115ms}  | range:[-inf,2), keep order:false                                                                                                                                                                          | N/A       | N/A     |
|   └─Selection_20(Probe)          | 2684354.56 | 0       | cop[tikv] |                        |                                                                                                                                                                                                                                                                                                                                                                                               | or(lt(test.t1.c1, 2), and(lt(test.t1.c2, 2), if(isnull(test.t1.c3), NULL, 1)))                                                                                                                            | N/A       | N/A     |
|     └─TableRowIDScan_19          | 3355443.20 | 0       | cop[tikv] | table:t1               |                                                                                                                                                                                                                                                                                                                                                                                               | keep order:false                                                                                                                                                                                          | N/A       | N/A     |
+----------------------------------+------------+---------+-----------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------+
11 rows in set, 1 warning (0.09 sec)

使用建議:

  • 觀測(cè)普通算子時(shí)候,主要觀察執(zhí)行時(shí)間,物理算子并發(fā),實(shí)際行數(shù)和估算行數(shù)差異,想想最優(yōu)的邏輯計(jì)劃應(yīng)該是什么樣的,條件有沒(méi)有充分利用,下推是否合理,物理選擇是不是恰當(dāng)。
  • 觀測(cè) IndexMerge/IndexLookup 算子時(shí)候,除了上述指標(biāo)外,還要觀測(cè) Cop Task 的數(shù)量,往往 Cop Nums 越多,在短時(shí)間回給 TiKV 造成大量的請(qǐng)求遲滯,最終導(dǎo)致 TiKV Load 升高,TiDB 延遲增加。

TiDB 優(yōu)化器新特性性能提升預(yù)告(v8.1~v8.4)

1: Instance Plan Cache 特性: 使用全局統(tǒng)一的實(shí)例級(jí)內(nèi)存緩存更多 Session 間共享 SQL 的執(zhí)行計(jì)劃,大幅提升全局 SQL 執(zhí)行計(jì)劃命中率,進(jìn)一步降低 CPU 的消耗,大幅提升數(shù)據(jù)庫(kù)整體穩(wěn)定性和性能。

**2: TiDB Predicate Analyze 特性:**通過(guò)參數(shù)開關(guān)控制收集 SQL 中 predicate columns 的行為,在表統(tǒng)計(jì)信息收集時(shí)只針對(duì) predicate columns 進(jìn)行收集,達(dá)到大幅降低收集統(tǒng)計(jì)信息對(duì) CPU 和內(nèi)存資源的消耗,以及大幅縮短單個(gè)表的統(tǒng)計(jì)信息時(shí)間,確保在有限的統(tǒng)計(jì)信息收集窗口完成更多的表的統(tǒng)計(jì)信息收集,為 SQL 執(zhí)行計(jì)劃生成提供更加準(zhǔn)確的環(huán)境,最終實(shí)現(xiàn)整體數(shù)據(jù)庫(kù)的穩(wěn)定性和 SQL 的性能更進(jìn)一步的提升。

3: Concurrently init stats 特性: 在 TiDB Server 啟動(dòng)時(shí)通過(guò)多任務(wù)并發(fā)的方式,快速將所有統(tǒng)計(jì)信息數(shù)據(jù)快速裝載到 TiDB Server 內(nèi)存中,有效避免和解決了因統(tǒng)計(jì)信息加載時(shí)延過(guò)長(zhǎng),導(dǎo)致生成錯(cuò)誤的 SQL 執(zhí)行計(jì)劃,從而引起的性能問(wèn)題,或者直接產(chǎn)生錯(cuò)誤 SQL 執(zhí)行計(jì)劃導(dǎo)致消耗大量?jī)?nèi)存,CPU,網(wǎng)絡(luò)資源,引起的數(shù)據(jù)庫(kù)不穩(wěn)定的問(wèn)題,進(jìn)一步提高了數(shù)據(jù)庫(kù)的可用性。

通過(guò)事前,事中,時(shí)候多個(gè)階段對(duì)統(tǒng)計(jì)信息裝載優(yōu)化,SQL 執(zhí)行計(jì)劃的緩存優(yōu)化,統(tǒng)計(jì)信息收集的優(yōu)化,大幅提升 TiDB 的性能,降低硬件資源的消耗,提升數(shù)據(jù)庫(kù)和應(yīng)用的性能穩(wěn)定性。

http://www.risenshineclean.com/news/5587.html

相關(guān)文章:

  • 網(wǎng)站流量怎么做的南京網(wǎng)站設(shè)計(jì)
  • 開發(fā)利用水資源安徽seo優(yōu)化
  • 網(wǎng)站分為四個(gè)步驟開發(fā)建設(shè)百度推廣費(fèi)用
  • 鄭州網(wǎng)站建設(shè)哪家公司便宜網(wǎng)絡(luò)營(yíng)銷運(yùn)營(yíng)推廣
  • 做趣味圖形的網(wǎng)站seo引擎搜索
  • 網(wǎng)站改版提示無(wú)需改版怎么推廣app讓人去下載
  • php怎么做網(wǎng)頁(yè)免費(fèi)seo工具
  • 手機(jī)端網(wǎng)站怎么做的今日重大國(guó)際新聞軍事
  • 如何做不同域名跳轉(zhuǎn)同一個(gè)網(wǎng)站軟文推廣例子
  • 中國(guó)知名設(shè)計(jì)網(wǎng)站國(guó)家職業(yè)技能培訓(xùn)平臺(tái)
  • 看守所加強(qiáng)自身網(wǎng)站建設(shè)工作服務(wù)推廣軟文
  • 網(wǎng)站建設(shè)全部流程包括備案免費(fèi)建站免費(fèi)網(wǎng)站
  • 做網(wǎng)站優(yōu)化詞怎么選擇電商網(wǎng)站平臺(tái)有哪些
  • php網(wǎng)站維護(hù)刷關(guān)鍵詞排名
  • 建一個(gè)購(gòu)物網(wǎng)站多少錢吳江seo網(wǎng)站優(yōu)化軟件
  • 百度做的網(wǎng)站能優(yōu)化嗎網(wǎng)站的seo 如何優(yōu)化
  • wordpress 做的網(wǎng)站全球中文網(wǎng)站排名
  • seo網(wǎng)站建設(shè)廈門2022千鋒教育培訓(xùn)收費(fèi)一覽表
  • 網(wǎng)站制作多少錢?個(gè)人網(wǎng)站制作教程
  • 類似情侶空間的網(wǎng)站開發(fā)制作網(wǎng)站平臺(tái)
  • 高端女裝有哪些品牌搜索引擎排名優(yōu)化seo
  • 銅川做網(wǎng)站電話顏色廣告
  • 燕郊網(wǎng)站建設(shè)公司企業(yè)網(wǎng)站推廣方案設(shè)計(jì)畢業(yè)設(shè)計(jì)
  • 南京 網(wǎng)站制作公司新網(wǎng)域名
  • 網(wǎng)站開發(fā)開票內(nèi)容寫什么產(chǎn)品關(guān)鍵詞大全
  • 林州網(wǎng)站建設(shè)拉新十大推廣app平臺(tái)
  • php網(wǎng)站優(yōu)點(diǎn)廈門seo培訓(xùn)
  • 做家教去哪個(gè)網(wǎng)站武漢seo價(jià)格
  • 企業(yè)網(wǎng)站建設(shè)與優(yōu)化深圳做推廣哪家比較好
  • 找大學(xué)生做家教去哪個(gè)網(wǎng)站找好關(guān)鍵詞seo深圳