重要
このページは英語版のページを機械翻訳しています。原文はこちらからご覧ください。

Predicate Push Down(PPD)

このドキュメントでは、TiDBのロジック最適化ルールの1つである述語プッシュダウン(PPD)を紹介します。これは、述語のプッシュダウンを理解し、その適用可能なシナリオと適用できないシナリオを理解するのに役立つことを目的としています。

PPDは、選択演算子をデータソースにできるだけ近づけて、データフィルタリングをできるだけ早く完了します。これにより、データの送信または計算のコストが大幅に削減されます。

次のケースでは、PPDの最適化について説明します。ケース1、2、および3はPPDが適用可能なシナリオであり、ケース4、5、および6はPPDが適用されないシナリオです。

ケース1:述語をストレージレイヤーにプッシュする

create table t(id int primary key, a int);
explain select * from t where a < 1;
+-------------------------+----------+-----------+---------------+--------------------------------+
| id                      | estRows  | task      | access object | operator info                  |
+-------------------------+----------+-----------+---------------+--------------------------------+
| TableReader_7           | 3323.33  | root      |               | data:Selection_6               |
| └─Selection_6           | 3323.33  | cop[tikv] |               | lt(test.t.a, 1)                |
|   └─TableFullScan_5     | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+---------------+--------------------------------+
3 rows in set (0.00 sec)

このクエリでは、述語a < 1をTiKVレイヤーにプッシュダウンしてデータをフィルタリングすると、ネットワーク送信のオーバーヘッドを減らすことができます。

ケース2:述語をストレージレイヤーにプッシュする

create table t(id int primary key, a int not null);
explain select * from t where a < substring('123', 1, 1);
+-------------------------+----------+-----------+---------------+--------------------------------+
| id                      | estRows  | task      | access object | operator info                  |
+-------------------------+----------+-----------+---------------+--------------------------------+
| TableReader_7           | 3323.33  | root      |               | data:Selection_6               |
| └─Selection_6           | 3323.33  | cop[tikv] |               | lt(test.t.a, 1)                |
|   └─TableFullScan_5     | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+---------------+--------------------------------+

このクエリの実行プランは、ケース1のクエリと同じです。これは、述語a < substring('123', 1, 1)substringの入力パラメーターが定数であるため、事前に計算できるためです。次に、述語は同等の述語a < 1に簡略化されます。その後、TiDBはa < 1をTiKVにプッシュダウンできます。

ケース3:結合演算子の下に述語をプッシュする

create table t(id int primary key, a int not null);
create table s(id int primary key, a int not null);
explain select * from t join s on t.a = s.a where t.a < 1;
+------------------------------+----------+-----------+---------------+--------------------------------------------+
| id                           | estRows  | task      | access object | operator info                              |
+------------------------------+----------+-----------+---------------+--------------------------------------------+
| HashJoin_8                   | 4154.17  | root      |               | inner join, equal:[eq(test.t.a, test.s.a)] |
| ├─TableReader_15(Build)      | 3323.33  | root      |               | data:Selection_14                          |
| │ └─Selection_14             | 3323.33  | cop[tikv] |               | lt(test.s.a, 1)                            |
| │   └─TableFullScan_13       | 10000.00 | cop[tikv] | table:s       | keep order:false, stats:pseudo             |
| └─TableReader_12(Probe)      | 3323.33  | root      |               | data:Selection_11                          |
|   └─Selection_11             | 3323.33  | cop[tikv] |               | lt(test.t.a, 1)                            |
|     └─TableFullScan_10       | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo             |
+------------------------------+----------+-----------+---------------+--------------------------------------------+
7 rows in set (0.00 sec)

このクエリでは、述語t.a < 1が結合の下にプッシュされて事前にフィルタリングされます。これにより、結合の計算オーバーヘッドを削減できます。

さらに、このSQLステートメントでは内部結合が実行され、 ONの条件はt.a = s.aです。述部s.a <1は、 t.a < 1から派生し、結合演算子の下のsテーブルにプッシュダウンできます。 sテーブルをフィルタリングすると、結合の計算オーバーヘッドをさらに削減できます。

ケース4:ストレージレイヤーでサポートされていない述語をプッシュダウンできない

create table t(id int primary key, a int not null);
desc select * from t where substring('123', a, 1) = '1';
+-------------------------+---------+-----------+---------------+----------------------------------------+
| id                      | estRows | task      | access object | operator info                          |
+-------------------------+---------+-----------+---------------+----------------------------------------+
| Selection_7             | 2.00    | root      |               | eq(substring("123", test.t.a, 1), "1") |
| └─TableReader_6         | 2.00    | root      |               | data:TableFullScan_5                   |
|   └─TableFullScan_5     | 2.00    | cop[tikv] | table:t       | keep order:false, stats:pseudo         |
+-------------------------+---------+-----------+---------------+----------------------------------------+

このクエリには、述語substring('123', a, 1) = '1'があります。

explainの結果から、述語が計算のためにTiKVにプッシュダウンされていないことがわかります。これは、TiKVコプロセッサーが組み込み関数substringをサポートしていないためです。

ケース5:外部結合の内部テーブルの述語をプッシュダウンできない

create table t(id int primary key, a int not null);
create table s(id int primary key, a int not null);
explain select * from t left join s on t.a = s.a where s.a is null;
+-------------------------------+----------+-----------+---------------+-------------------------------------------------+
| id                            | estRows  | task      | access object | operator info                                   |
+-------------------------------+----------+-----------+---------------+-------------------------------------------------+
| Selection_7                   | 10000.00 | root      |               | isnull(test.s.a)                                |
| └─HashJoin_8                  | 12500.00 | root      |               | left outer join, equal:[eq(test.t.a, test.s.a)] |
|   ├─TableReader_13(Build)     | 10000.00 | root      |               | data:TableFullScan_12                           |
|   │ └─TableFullScan_12        | 10000.00 | cop[tikv] | table:s       | keep order:false, stats:pseudo                  |
|   └─TableReader_11(Probe)     | 10000.00 | root      |               | data:TableFullScan_10                           |
|     └─TableFullScan_10        | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo                  |
+-------------------------------+----------+-----------+---------------+-------------------------------------------------+
6 rows in set (0.00 sec)

このクエリでは、内部テーブルsに述語s.a is nullがあります。

explainの結果から、述語が結合演算子の下にプッシュされていないことがわかります。これは、 on条件が満たされない場合、外部結合が内部テーブルにNULLの値を入力し、述部s.a is nullが結合後の結果をフィルタリングするために使用されるためです。結合の下の内部テーブルにプッシュダウンされた場合、実行プランは元のプランと同等ではありません。

ケース6:ユーザー変数を含む述語をプッシュダウンできない

create table t(id int primary key, a char);
set @a = 1;
explain select * from t where a < @a;
+-------------------------+----------+-----------+---------------+--------------------------------+
| id                      | estRows  | task      | access object | operator info                  |
+-------------------------+----------+-----------+---------------+--------------------------------+
| Selection_5             | 8000.00  | root      |               | lt(test.t.a, getvar("a"))      |
| └─TableReader_7         | 10000.00 | root      |               | data:TableFullScan_6           |
|   └─TableFullScan_6     | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+---------------+--------------------------------+
3 rows in set (0.00 sec)

このクエリでは、テーブルtに述語a < @aがあります。述語の@aはユーザー変数です。

explainの結果からわかるように、述語はケース2とは異なり、 a < 1に簡略化され、TiKVにプッシュダウンされます。これは、ユーザー変数@aの値が計算中に変更される可能性があり、TiKVがその変更を認識しないためです。したがって、TiDBは@a1に置き換えたり、TiKVにプッシュダウンしたりしません。

理解しやすい例は次のとおりです。

create table t(id int primary key, a int);
insert into t values(1, 1), (2,2);
set @a = 1;
select id, a, @a:=@a+1 from t where a = @a;
+----+------+----------+
| id | a    | @a:=@a+1 |
+----+------+----------+
|  1 |    1 | 2        |
|  2 |    2 | 3        |
+----+------+----------+
2 rows in set (0.00 sec)

このクエリからわかるように、値@aはクエリ中に変更されます。したがって、 a = @aa = 1に置き換えてTiKVにプッシュダウンした場合、それは同等の実行プランではありません。