摘要: 本文著重介紹 DRDS 執行計劃中各個操作符的含義,以便用戶通過查詢計划了解 SQL 執行流程,從而有針對性的調優 SQL。 DRDS分散式SQL引擎 — 執行計劃介紹 前言 資料庫系統中,執行計劃是對 SQL 如何執行的形式化表示,往往由若幹關係操作符構成,用戶可以通過對應的 EXPLAI ...
摘要: 本文著重介紹 DRDS 執行計劃中各個操作符的含義,以便用戶通過查詢計划了解 SQL 執行流程,從而有針對性的調優 SQL。
DRDS分散式SQL引擎 — 執行計劃介紹
前言
資料庫系統中,執行計劃是對 SQL 如何執行的形式化表示,往往由若幹關係操作符構成,用戶可以通過對應的 EXPLAIN 命令查看,並通過執行計劃大致瞭解 SQL 的執行過程和執行方式,如全表掃描還是索引掃描,歸併連接還是哈希連接等。執行計劃可以為用戶進行 SQL 調優提供重要依據。
DRDS 執行計劃
與多數資料庫系統類似,DRDS 在處理 SQL 時,會通過優化器生成執行計劃,該執行計劃由關係操作符構成一個樹形結構,反映 DRDS 如何執行 SQL 語句;不同的是,DRDS 本身不存儲數據,更側重考慮分散式環境中的網路 IO 開銷,將運算下推到各個分庫(如 RDS/MySQL)執行,從而提升 SQL 執行效率。用戶可通過 EXPLAIN 命令查看 SQL 的執行計劃。
本文著重介紹 DRDS 執行計劃中各個操作符的含義,以便用戶通過查詢計划了解 SQL 執行流程,從而有針對性的調優 SQL。文中示例均基於如下表結構:
CREATE TABLE `sbtest1` (`id` INT(10) UNSIGNED NOT NULL,`k` INT(10) UNSIGNED NOT NULL DEFAULT '0',`c` CHAR(120) NOT NULL DEFAULT '',`pad` CHAR(60) NOT NULL DEFAULT '',KEY `xid` (`id`),KEY `k_1` (`k`)) dbpartition BY HASH (`id`) tbpartition BY HASH (`id`) tbpartitions 4
先通過一個例子整體瞭解 DRDS 執行計劃的樹形結構。
mysql> explain select a.k, count(*) cnt from sbtest1 a, sbtest1 b where a.id = b.k and a.id > 1000 group by k having cnt > 1300 order by cnt limit 5, 10;+---------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+---------------------------------------------------------------------------------------------------------------------------------------------------+| TmpSort(sort="cnt ASC", offset=?2, fetch=?3) || Filter(condition="cnt > ?1") || Aggregate(group="k", cnt="COUNT()") || BKAJoin(id="id", k="k", c="c", pad="pad", id0="id0", k0="k0", c0="c0", pad0="pad0", condition="id = k", type="inner") || MergeSort(sort="k ASC") || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?) ORDER BY `k`") || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE ((`k` > ?) AND (`k` IN ('?')))") || HitCache:false |+---------------------------------------------------------------------------------------------------------------------------------------------------+9 rows in set (0.01 sec)
如上,DRDS EXPLAIN 的結果總體分為兩部分:執行計劃和其他信息。
-
執行計劃
執行計劃以縮進形式表示操作符之間的 "父-子" 關係。示例中,Filter 是 TmpSort 的子操作符,同時是 Aggregate 的父操作符。從真正執行的角度看,每個操作符均從其子操作符中獲取數據,經當前操作符處理,輸出給其父操作符。為方便理解,將以上執行計劃轉換為更加直觀的樹形結構:
-
其他信息
除執行計劃外,EXPLAIN 結果中還會有一些額外信息,目前僅有一項 `HitCache` 。需要說明的是,DRDS 會預設開啟 PlanCache 功能,`HitCache` 表示當前 SQL 是否命中 PlanCache。
開啟 PlanCache 後,DRDS 會對 SQL 做參數化處理,參數化會將 SQL 中的大部分常量用
?
替換,並構建一個參數列表。在執行計劃中的體現就是,LogicalView 的sql
中會有?
,在部分操作符中會有類似?2
的字樣,這裡的2
表示其在參數列表中的下標,後續會結合具體的例子進一步闡述。
EXPLAIN 語法
EXPLAIN 用於查看 SQL 語句的執行計劃,語法如下:
EXPLAIN explainable_stmtexplainable_stmt: {SELECT statement| DELETE statement| INSERT statement| REPLACE statement| UPDATE statement}
操作符介紹
本小節詳細介紹 DRDS 執行計劃中各個操作符的含義。
LogicalView
LogicalView 是從底層數據源獲取數據的操作符。從資料庫的角度來看,使用 TableScan
命名更符合常規,但考慮到 DRDS 本身不存儲數據,而是通過 SQL 從底層數據源獲取,因此,該操作符中會記錄下推的 SQL 語句和數據源信息,這更像一個 "視圖"。該 "視圖" 中的 SQL,通過優化器的下推,可能包含多種操作,如投影、過濾、聚合、排序、連接和子查詢等。
以下通過示例說明 EXPLAIN 中 LogicalView 的輸出信息及其含義:
mysql> explain select * From sbtest1 where id > 1000;+-----------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-----------------------------------------------------------------------------------------------------------------------+| UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)") || HitCache:false |+-----------------------------------------------------------------------------------------------------------------------+3 rows in set (0.00 sec)
LogicalView 的信息由三部分構成:
-
tables:底層數據源對應的表名,以
.
分割,其前是分庫對應的編號,其後是表名及其編號,對於連續的編號,會做簡寫,如[000-127]
,表示表名編號從000
到127
的所有表。 -
shardCount:需要訪問的分表總數,該示例中會訪問從
000
到127
共 128 張分表。 -
sql:下發至底層數據源的 SQL 模版。這裡顯示的並非真正下發的 SQL 語句,DRDS 在執行時會將表名替換為物理表名;另外,SQL 中的常量
10
被?
替換,這是因為 DRDS 預設開啟了 PlanCache 功能,對 SQL 做了參數化處理。
UnionAll
UnionAll 是 UNION ALL
對應的操作符,該操作符通常有多個輸入,表示將多個輸入的數據 UNION 在一起。以上示例中,LogicalView 之上的 UnionAll 表示將所有分表中的數據進行 UNION。
UnionAll 中的 concurrent
表示是否並行執行其子操作符,預設為 true。
UnionDistinct
與 UnionAll 類似,UnionDistinct 是 UNION DISTINCT
對應的操作符。如下:
mysql> explain select * From sbtest1 where id > 1000 union distinct select * From sbtest1 where id < 200;+-------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-------------------------------------------------------------------------------------------------------------------------+| UnionDistinct(concurrent=true) || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)") || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` < ?)") || HitCache:false |+-------------------------------------------------------------------------------------------------------------------------+6 rows in set (0.02 sec)
MergeSort
MergeSort,歸併排序操作符,通常有多個子操作符。DRDS 中實現了兩種排序:基於有序數據的歸併排序和對無序數據的記憶體排序。如下:
mysql> explain select *from sbtest1 where id > 1000 order by id limit 5,10;+---------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+---------------------------------------------------------------------------------------------------------------------------------------------------+| MergeSort(sort="id ASC", offset=?1, fetch=?2) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?) ORDER BY `id` LIMIT (? + ?)") || HitCache:false |+---------------------------------------------------------------------------------------------------------------------------------------------------+3 rows in set (0.00 sec)
MergeSort 操作符包含三部分內容:
-
sort:表示排序欄位以及排列順序,
id ASC
表示按照id
欄位遞增排序,DESC
表示遞減排序。 -
offset:表示獲取結果集時的偏移量,同樣由於對 SQL 做了參數化,示例中的
offst
表示為?1
,其中?
表示這是一個動態參數,其後的數字對應參數列表的下標。示例中 SQL 對應的參數為[1000, 5, 10]
,因此,?1
實際對應的值為5
。 -
fetch:表示最多返回的數據行數。與
offset
類似,同樣是參數化的表示,實際對應的值為10
。
Aggregate
Aggregate 是聚合操作符,通常包含兩部分內容:Group By 欄位和聚合函數。如下:
mysql> explain select k, count(*) from sbtest1 where id > 1000 group by k;+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Aggregate(group="k", count(*)="SUM(count(*))") || MergeSort(sort="k ASC") || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k`, COUNT(*) AS `count(*)` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`") || HitCache:true |+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+4 rows in set (0.00 sec)
Aggregate 包含兩部分內容:
-
group:表示 GROUP BY 欄位,示例中為
k
。 -
聚合函數:
=
前為聚合函數對應的輸出列名,其後為對應的計算方法。示例中count(*)="SUM(count(*))"
,第一個count(*)
對應輸出的列名,隨後的SUM(count(*))
表示對其輸入數據中的count(*)
列進行SUM
運算得到最終的count(*)
。
由此可見,DRDS 將聚合操作分為兩部分,首先將聚合操作下推至底層數據源做局部聚合,最終在 DRDS 層面對局部聚合的結果做全局聚合。另外,DRDS 的最終聚合是基於排序做的,因此,會在優化器階段為其添加一個 Sort
子操作符,而 Sort
操作符又進一步通過下推 Sort 轉換為 MergeSort
。
再來看一個 AVG
聚合函數的例子,如下:
mysql> explain select k, avg(id) avg_id from sbtest1 where id > 1000 group by k;+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN|+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Project(k="k", avg_id="sum_pushed_sum / sum_pushed_count")|| Aggregate(group="k", sum_pushed_sum="SUM(pushed_sum)", sum_pushed_count="SUM(pushed_count)")|| MergeSort(sort="k ASC")|| LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k`, SUM(`id`) AS `pushed_sum`, COUNT(`id`) AS `pushed_count` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`")|| HitCache:false|+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+5 rows in set (0.01 sec)
DRDS 會將 AVG
聚合函數轉換為 SUM / COUNT
,再分別根據 SUM
和 COUNT
的下推規則,將其轉換為局部聚合和全局聚合。用戶可自行嘗試瞭解其他聚合函數的執行計劃。
註意:DRDS 會將 DISTINCT
操作轉換為 GROUP
操作,如下:
mysql> explain select distinct k from sbtest1 where id > 1000;+-----------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-----------------------------------------------------------------------------------------------------------------------------------------------------+| Aggregate(group="k") || MergeSort(sort="k ASC") || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`") || HitCache:false |+-----------------------------------------------------------------------------------------------------------------------------------------------------+4 rows in set (0.02 sec)
TmpSort
TmpSort,表示在記憶體中對數據進行排序。與 MergeSort 的區別在於,MergeSort 可以有多個子操作符,且每個子操作符返回的數據都已經排序。TmpSort 僅有一個子操作符。
TmpSort 對應的查詢計劃信息與 MergeSort 一致,請參考 MergeSort。
Project
Project 表示投影操作,即從輸入數據中選擇部分列輸出,或者對某些列進行轉換(通過函數或者表達式計算)後輸出,當然,也可以包含常量。以上 AVG
的示例中,最頂層就是一個 Project
,其輸出 k
和 sum_pushed_sum / sum_pushed_count
,後者對應的列名為 avg_id
。
mysql> explain select '你好, DRDS', 1 / 2, CURTIME();+-------------------------------------------------------------------------------------+| LOGICAL PLAN |+-------------------------------------------------------------------------------------+| Project(你好, DRDS="_UTF-16'你好, DRDS'", 1 / 2="1 / 2", CURTIME()="CURTIME()") || || HitCache:false |+-------------------------------------------------------------------------------------+3 rows in set (0.00 sec)
可見,Project 的計劃中包括每列的列名及其對應的列、值、函數或者表達式。
Filter
Filter 表示過濾操作,其中包含一些過濾條件。該操作符對輸入數據進行過濾,若滿足條件,則輸出,否則丟棄。如下是一個較複雜的例子,包含了以上介紹的大部分操作符。
mysql> explain select k, avg(id) avg_id from sbtest1 where id > 1000 group by k having avg_id > 1300;+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Filter(condition="avg_id > ?1") || Project(k="k", avg_id="sum_pushed_sum / sum_pushed_count") || Aggregate(group="k", sum_pushed_sum="SUM(pushed_sum)", sum_pushed_count="SUM(pushed_count)") || MergeSort(sort="k ASC") || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k`, SUM(`id`) AS `pushed_sum`, COUNT(`id`) AS `pushed_count` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`") || HitCache:false |+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+6 rows in set (0.01 sec)
在以上 AVG
示例的 SQL 基礎上添加了having avg_id > 1300
,執行計劃最上層添加了一個 Filter 操作符,用於過濾所有滿足 avg_id > 1300
的數據。
有讀者可能會問,WHERE 中的條件為什麼沒有對應的 Filter 操作符呢?在 DRDS 優化器的某個階段,WHERE 條件的 Filter 操作符的確是存在的,只是最終將其下推到了 LogiacalView 中,因此可以在 LogicalView 的 sql
中看到 id > 1000
。
NlJoin
NlJoin,表示 NestLoop Join 操作符,即使用 NestLoop 方法進行兩表 Join。DRDS 中實現了兩種 JOIN 策略:NlJoin 和 BKAJoin,後者表示 Batched Key Access Join,批量鍵值查詢,會從左表取一批數據,構建一個 IN 條件拼接在訪問右表的 SQL 中,從右表一次獲取一批數據。
mysql> explain select a.* from sbtest1 a, sbtest1 b where a.id = b.k and a.id > 1000;+----------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+----------------------------------------------------------------------------------------------------------------------------+| Project(id="id", k="k", c="c", pad="pad") || NlJoin(id="id", k="k", c="c", pad="pad", k0="k0", condition="id = k", type="inner") || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)") || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k` FROM `sbtest1` WHERE (`k` > ?)") || HitCache:false |+----------------------------------------------------------------------------------------------------------------------------+7 rows in set (0.03 sec)
NlJOIN 的計劃包括三部分內容:
-
輸出列信息:輸出的列名,示例中的 JOIN 會輸出 5 列
id="id", k="k", c="c", pad="pad", k0="k0"
。 -
contition:連接條件,示例中連接條件為
id = k
。 -
type:連接類型,示例中是 INNER JOIN,因此其連接類型為
inner
。
BKAJoin
BKAJoin,Batched Key Access Join,表示通過批量鍵值查詢的方式進行 JOIN,即從左表取一批數據,構建一個 IN 條件拼接在訪問右表的 SQL 中,從右表一次獲取一批數據進行 JOIN。
mysql> explain select a.* from sbtest1 a, sbtest1 b where a.id = b.k order by a.id;+-------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-------------------------------------------------------------------------------------------------------------------------------+| Project(id="id", k="k", c="c", pad="pad") || BKAJoin(id="id", k="k", c="c", pad="pad", id0="id0", k0="k0", c0="c0", pad0="pad0", condition="id = k", type="inner") || MergeSort(sort="id ASC") || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` ORDER BY `id`") || UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`k` IN ('?'))") || HitCache:false |+-------------------------------------------------------------------------------------------------------------------------------+7 rows in set (0.01 sec)
BKAJoin 的計劃內容與 NlJoin 相同,這兩個操作符命名不同,旨在告知執行器以何種方法執行 JOIN 操作。另外,以上執行計劃中右表的 LogicalView 中 k IN ('?')
是優化器構建出來的對右表的IN查詢模板。
LogicalModifyView
如上文介紹,LogicalView 表示從底層數據源獲取數據的操作符,與之對應的,LogicalModifyView 表示對底層數據源的修改操作符,其中也會記錄一個 SQL 語句,該 SQL 可能是 INSERT、UPDATE 或者 DELETE。
mysql> explain update sbtest1 set c='Hello, DRDS' where id > 1000;+--------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+--------------------------------------------------------------------------------------------------------------------------------+| LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="UPDATE `sbtest1` SET `c` = ? WHERE (`id` > ?)") || HitCache:false |+--------------------------------------------------------------------------------------------------------------------------------+2 rows in set (0.03 sec)mysql> explain delete from sbtest1 where id > 1000;+-------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-------------------------------------------------------------------------------------------------------------------------+| LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="DELETE FROM `sbtest1` WHERE (`id` > ?)") || HitCache:false |+-------------------------------------------------------------------------------------------------------------------------+2 rows in set (0.03 sec)
LogicalModifyView 查詢計劃的內容與 LogicalView 類似,包括下發的物理分表,分表數以及 SQL 模版。同樣,由於開啟了 PlanCache,對 SQL 做了參數化處理,SQL 模版中的常量會用 ?
替換。
PhyTableOperation
PhyTableOperation 表示對某個物理分表執行一個操作。該操作符目前僅用於 INSERT INTO ... VALUES ...。
mysql> explain insert into sbtest1 values(1, 1, '1', '1'),(2, 2, '2', '2');+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_001]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_001`,1,1,1,1") || PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_002]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_002`,2,2,2,2") || || HitCache:false |+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+4 rows in set (0.00 sec)
示例中,INSERT 插入兩行數據,每行數據對應一個 PhyTableOperation 操作符,PhyTableOperation 操作符的內容包括三部分:
-
tables:物理表名,僅有唯一一個物理表名。
-
sql:SQL 模版,該 SQL 模版中表名和常量均被參數化,用
?
替換,對應的參數在隨後的 params 中給出。 -
params:SQL 模版對應的參數,包括表名和常量。
其他信息
HitCache
DRDS 會預設開啟 PlanCache 功能,HitCache 用於告知用戶當前查詢是否命中 PlanCache。如下,第一次運行 HitCache 為 false,第二次運行為 true。
mysql> explain select * From sbtest1 where id > 1000;+-----------------------------------------------------------------------------------------------------------------------+| LOGICAL PLAN |+-----------------------------------------------------------------------------------------------------------------------+| UnionAll(concurrent=true) || LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)") || HitCache:false |+-----------------------------------------------------------------------------------------------------------------------+3 rows in&n