本文介紹了PolarDB-X處理SQL的基本原理及執行計畫的概念。
分散式資料庫架構相對單機資料庫有差異,所以在單機資料庫調優方法的基礎上分散式資料庫又有著自身的特點。PolarDB-X可以基於統計資訊、執行計畫、並發策略和執行之後反饋的運行時間長度等資訊,找出導致SQL執行慢的原因,進行針對性調優。
一條慢查詢可能和物理SQL執行快慢、並發度數量、執行計畫和索引選擇是否合適都有關係。所以在分散式資料庫中,SQL調優的成本一般會比單機資料庫高。
基本原理
PolarDB-X是一款計算儲存分離的分散式資料庫產品。當一條查詢SQL發往PolarDB-X計算節點(CN)時(此SQL又稱為邏輯SQL),PolarDB-X會將其分成可下推和不可下推的兩部分,可下推的部分也被稱為物理SQL。不可下推的SQL在CN上執行,可下推的SQL在DN上執行。
PolarDB-X按照以下原則進行SQL最佳化:
儘可能將邏輯SQL下推到DN上執行,除了可以避免CN和DN間資料網路互動以外,還可以充分利用多分區並發執行的能力,利用各個DN資源加速查詢。
對於無法下推的部分包含的物理運算元,查詢最佳化工具會選擇最優的方式來執行,例如選擇合適的物理運算元,選擇合適的並行度策略以及使用MPP執行。
說明並行度指查詢過程中資料並存執行的最大數目,對於CN來說就是利用多核能力多線程計算,對DN來說就是同時執行多個下推物理SQL的並行數。
PolarDB-X的索引一般分為局部索引和全域索引,局部索引是指單個DN節點的索引(MySQL索引),全域索引是指構建在多個DN上的分布式索引。選擇合適的索引,可以極大提高PolarDB-X的檢索速度。
執行計畫介紹
一條SQL進入到PolarDB-X分散式資料庫後,經過解析最佳化,會產生一個可啟動並執行執行計畫。該執行計畫是按照執行過程中運算元的依賴關係組成。一般通過執行計畫樹,可以窺探SQL在資料庫內部是如何高效啟動並執行。樣本如下:
樣本1
執行以下命令:
EXPLAIN select count(*) from lineitem group by L_LINESTATUS;返回執行計畫資訊如下:
| HashAgg(group="L_LINESTATUS", count(*)="SUM(count(*))") | | Exchange(distribution=hash[0], collation=[]) | | LogicalView(tables="[000000-000003].lineitem_[00-15]", shardCount=16, sql="SELECT `L_LINESTATUS`, COUNT(*) AS `count(*)` FROM `lineitem` AS `lineitem` GROUP BY `L_LINESTATUS`")由於group by的列和表lineitem分區鍵不對齊,group by不能完全下推給DN執行。所以group by會拆分成兩階段,將partition agg下推給DN,先做部分彙總;然後在CN層將資料做重分布式,再做一次最終的彙總,輸出結果。
LogicalView:由於有16個分區,會產生多個下推物理SQL,每條物理SQL攜帶了group by部分,做預彙總。
Exchange:匯總LogicalView返回的資料,按照L_LINESTATUS欄位做重分布式,輸出給下遊運算元。
HashAgg:接受多路輸入,做最終的彙總。
樣本2
執行以下命令:
EXPLAIN select * from lineitem, orders where L_ORDERKEY= O_ORDERKEY;返回執行計畫如下:
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | HashJoin(condition="O_ORDERKEY = L_ORDERKEY", type="inner") | | Exchange(distribution=hash[0], collation=[]) | | LogicalView(tables="[000000-000003].lineitem_[00-15]", shardCount=16, sql="SELECT `L_ORDERKEY`, `L_PARTKEY`, `L_SUPPKEY`, `L_LINENUMBER`, `L_QUANTITY`, `L_EXTENDEDPRICE`, `L_DISCOUNT`, `L_TAX`, `L_RETURNFLAG`, `L_LINESTATUS`, `L_SHIPDATE`, `L_COMMITDATE`, `L_RECEIPTDATE`, `L_SHIPINSTRUCT`, `L_SHIPMODE`, `L_COMMENT` FROM `lineitem` AS `lineitem`") | | Exchange(distribution=hash[0], collation=[]) | | LogicalView(tables="[000000-000003].orders_[00-15]", shardCount=16, sql="SELECT `O_ORDERKEY`, `O_CUSTKEY`, `O_ORDERSTATUS`, `O_TOTALPRICE`, `O_ORDERDATE`, `O_ORDERPRIORITY`, `O_CLERK`, `O_SHIPPRIORITY`, `O_COMMENT` FROM `orders` AS `orders`")樣本2是典型的兩張表做關聯(join),由於兩表分區鍵沒對齊,所有join沒有下推,整個執行過程是將兩個表資料都掃描出來,在CN層做關聯計算。
LogicalView:掃描表資料。
Exchange:匯總LogicalView返回的資料,分布按照關聯條件的列做重分布式,輸出給下遊Join運算元。
HashJoin:接受兩邊輸入,通過構建HashTable的方式來計算關連接果。
樣本3
執行以下命令:
EXPLAIN select * from lineitem, orders where L_LINENUMBER= O_ORDERKEY;返回執行計畫資訊如下:
| Gather(concurrent=true) | | LogicalView(tables="[000000-000003].lineitem_[00-15],orders_[00-15]", shardCount=16, sql="SELECT `lineitem`.`L_ORDERKEY`, `lineitem`.`L_PARTKEY`, `lineitem`.`L_SUPPKEY`, `lineitem`.`L_LINENUMBER`, `lineitem`.`L_QUANTITY`, `lineitem`.`L_EXTENDEDPRICE`, `lineitem`.`L_DISCOUNT`, `lineitem`.`L_TAX`, `lineitem`.`L_RETURNFLAG`, `lineitem`.`L_LINESTATUS`, `lineitem`.`L_SHIPDATE`, `lineitem`.`L_COMMITDATE`, `lineitem`.`L_RECEIPTDATE`, `lineitem`.`L_SHIPINSTRUCT`, `lineitem`.`L_SHIPMODE`, `lineitem`.`L_COMMENT`, `orders`.`O_ORDERKEY`, `orders`.`O_CUSTKEY`, `orders`.`O_ORDERSTATUS`, `orders`.`O_TOTALPRICE`, `orders`.`O_ORDERDATE`, `orders`.`O_ORDERPRIORITY`, `orders`.`O_CLERK`, `orders`.`O_SHIPPRIORITY`, `orders`.`O_COMMENT` FROM `lineitem` AS `lineitem` INNER JOIN `orders` AS `orders` ON (`lineitem`.`L_LINENUMBER` = `orders`.`O_ORDERKEY`)") |樣本3也是典型的兩張表做關聯,由於兩表分區鍵對齊,所有join下推到各個分區的DN來執行,上層的CN節點只需要通過Gather運算元將DN返回的結果匯總輸出。
樣本4
執行以下命令:
EXPLAIN select * from gsi_dml_unique_multi_index_base where integer_test=1;返回執行計畫資訊如下:
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Project(pk="pk", integer_test="integer_test", varchar_test="varchar_test", char_test="char_test", blob_test="blob_test", tinyint_test="tinyint_test", tinyint_1bit_test="tinyint_1bit_test", smallint_test="smallint_test", mediumint_test="mediumint_test", bit_test="bit_test", bigint_test="bigint_test", float_test="float_test", double_test="double_test", decimal_test="decimal_test", date_test="date_test", time_test="time_test", datetime_test="datetime_test", timestamp_test="timestamp_test", year_test="year_test", mediumtext_test="mediumtext_test") | | BKAJoin(condition="pk = pk", type="inner") | | IndexScan(tables="DRDS_POLARX1_QATEST_APP_000000_GROUP.gsi_dml_unique_multi_index_index1_a0ol_01", sql="SELECT `pk`, `integer_test`, `varchar_test`, `char_test`, `bit_test`, `bigint_test`, `double_test`, `date_test` FROM `gsi_dml_unique_multi_index_index1` AS `gsi_dml_unique_multi_index_index1` WHERE (`integer_test` = ?)") | | Gather(concurrent=true) | | LogicalView(tables="[000000-000003].gsi_dml_unique_multi_index_base_[00-15]", shardCount=16, sql="SELECT `pk`, `blob_test`, `tinyint_test`, `tinyint_1bit_test`, `smallint_test`, `mediumint_test`, `float_test`, `decimal_test`, `time_test`, `datetime_test`, `timestamp_test`, `year_test`, `mediumtext_test` FROM `gsi_dml_unique_multi_index_base` AS `gsi_dml_unique_multi_index_base` WHERE ((`integer_test` = ?) AND (`pk` IN (...)))") | | HitCache:true本樣本中,SQL本身只是帶有謂詞的簡單查詢,結果從執行計畫看是兩表做關聯(BKAJoin)。主要是gsi_dml_unique_multi_index_base在列上integer_test有全域二級索引,命中索引可以減少掃描代價,但這個索引並不是覆蓋索引,所以需要有回表操作。
IndexScan:根據integer_test=1掃描出索引表gsi_dml_unique_multi_index_index1_a0ol_01資料。
BKAJoin:收集IndexScan的結果,通過該運算元和主表gsi_dml_unique_multi_index_base做回表關聯,擷取其他列值。
說明通常情況下,通過查詢執行計畫,可以查看到是否命中了全域二級索引等資訊。但是對於下推部分的SQL,還可以通過explain execute指令,擷取物理SQL在DN上的執行情況,比如是否命中了DN的局部索引。