這一章主要涉及TiDB如下的源碼:
1. 掃表算子怎樣轉換為掃索引算子;
2. 怎樣把Selection算子的過濾條件化簡, 轉為區間掃描;
假設我們有一個表:
t1( id int primary key not null auto_increment, a int, b int, c varchar(256), index(a) );
其中, id 是主鍵, a 是索引;
我們執行如下的 sql:
select a from t1 where a=5 or ( a>5 and (a>6 and a <8) and a<12);
這條 sql 的最終執行計划是這樣的:
+---------------+--------+-----------+-----------------------------------------------------------------------+ | id | count | task | operator info | +---------------+--------+-----------+-----------------------------------------------------------------------+ | IndexReader_6 | 260.00 | root | index:IndexScan_5 | | └─IndexScan_5 | 260.00 | cop[tikv] | table:t1, index:a, range:[5,5], (6,8), keep order:false, stats:pseudo | +---------------+--------+-----------+-----------------------------------------------------------------------+
這是一個索引掃描的執行計划, 索引掃描區間是 [5,5], (6,8);
我們轉到源代碼, 看這樣的計划是怎樣生成的;
這是解析 sql 之后最初生成的執行計划:
在調用 logicalOptimize 函數做邏輯優化之后, 執行計划變為下面這樣:
Selection算子哪兒去了?
Selection算子被下推到了 DataSource 算子中, 在 DataSource 的 pushedDownConds 中保存着下推的過濾算子, 是這樣的:
這樣的一個遞歸的樹狀的過濾算子很難在索引掃描中使用, 因為索引底層是順序排列的, 所以要將這顆樹轉為掃描區間;
在物理優化中, 會調用 DetachCondAndBuildRangeForIndex 來生成掃描區間, 這個函數會遞歸的調用如下 2 個函數:
detachDNFCondAndBuildRangeForIndex, 展開 析取范式(DNF), 生成掃描區間或合並掃描區間;
detachCNFCondAndBuildRangeForIndex, 展開 合取范式(CNF), 生成掃描區間或合並掃描區間;
上面的表達式樹最終生成了這樣的區間: [5,5], (6,8) --- "[" 是開區間, "(" 是閉區間, 遞歸被消除了;
接下來, 這個索引掃描會加入到 DataSource 的備選的訪問表的方法中;
在 DataSource 的 possibleAccessPaths 里保存了訪問表的可能的方案, 這里是 2 個方案:
1. 全表掃描, 用表達式樹進行過濾: a=5 or ( a>5 and (a>6 and a <8) and a<12);
2. 掃索引 a 列, 執行區間掃描 [5,5], (6,8);
物理優化階段, 會從算子樹的根節點遞歸調用每個算子的 findBestTask 函數, DataSoure 算子會從 possibleAccessPaths 獲取最優的執行計划;
這里用到了 skyline pruning 算法, 從多個維度來判斷哪個執行計划更優, 最后用索引掃描算子替換掉 DataSource 算子;
最終生成了這樣的執行計划:
結束;