最大/最小を排除する
SQLステートメントにmax
関数が含まれている場合、クエリmin
はmin
最適化ルールを適用してmin
集計関数をmax
演算子に変換しようとしmax
。このようにして、TiDBはインデックスを介してより効率的にクエリを実行できます。
この最適化ルールは、 min
ステートメントのmax
関数の数に応じて、次の2 select
のタイプに分けられます。
1つのmax
/ min
関数
SQLステートメントが次の条件を満たす場合、このルールが適用されます。
- ステートメントには、
max
またはmin
の1つの集計関数のみが含まれています。 - 集計関数には、関連する
group by
句はありません。
例えば:
select max(a) from t
最適化ルールは、ステートメントを次のように書き直します。
select max(a) from (select a from t where a is not null order by a desc limit 1) t
列a
にインデックスがある場合、または列a
が複合インデックスのプレフィックスである場合、インデックスを使用して、新しいSQLステートメントは1行のデータのみをスキャンすることで最大値または最小値を見つけることができます。この最適化により、全表スキャンが回避されます。
サンプルステートメントには、次の実行プランがあります。
mysql> explain select max(a) from t;
+------------------------------+---------+-----------+-------------------------+-------------------------------------+
| id | estRows | task | access object | operator info |
+------------------------------+---------+-----------+-------------------------+-------------------------------------+
| StreamAgg_13 | 1.00 | root | | funcs:max(test.t.a)->Column#4 |
| └─Limit_17 | 1.00 | root | | offset:0, count:1 |
| └─IndexReader_27 | 1.00 | root | | index:Limit_26 |
| └─Limit_26 | 1.00 | cop[tikv] | | offset:0, count:1 |
| └─IndexFullScan_25 | 1.00 | cop[tikv] | table:t, index:idx_a(a) | keep order:true, desc, stats:pseudo |
+------------------------------+---------+-----------+-------------------------+-------------------------------------+
5 rows in set (0.00 sec)
複数max
/ min
関数
SQLステートメントが次の条件を満たす場合、このルールが適用されます。
- ステートメントには、すべて
max
つまたはmin
の関数である複数の集計関数が含まれています。 - どの集計関数にも関連する
group by
節はありません。 - 各
max
関数の列には、順序を保持するためのインデックスがありmin
。
例えば:
select max(a) - min(a) from t
最適化ルールは、最初に列a
にその順序を保持するためのインデックスがあるかどうかをチェックします。はいの場合、SQLステートメントは2つのサブクエリのデカルト積として書き直されます。
select max_a - min_a
from
(select max(a) as max_a from t) t1,
(select min(a) as min_a from t) t2
書き換えにより、オプティマイザは、2つのサブクエリにそれぞれmin
関数がmax
つしかないステートメントのルールを適用できます。次に、ステートメントは次のように書き直されます。
select max_a - min_a
from
(select max(a) as max_a from (select a from t where a is not null order by a desc limit 1) t) t1,
(select min(a) as min_a from (select a from t where a is not null order by a asc limit 1) t) t2
同様に、列a
にその順序を保持するためのインデックスがある場合、最適化された実行は、テーブル全体ではなく、2行のデータのみをスキャンします。ただし、列a
に順序を保持するためのインデックスがない場合、このルールは2回の全表スキャンになりますが、書き換えがない場合、実行に必要なのは1回の全表スキャンのみです。したがって、このような場合、このルールは適用されません。
最終的な実行計画は次のとおりです。
mysql> explain select max(a)-min(a) from t;
+------------------------------------+---------+-----------+-------------------------+-------------------------------------+
| id | estRows | task | access object | operator info |
+------------------------------------+---------+-----------+-------------------------+-------------------------------------+
| Projection_17 | 1.00 | root | | minus(Column#4, Column#5)->Column#6 |
| └─HashJoin_18 | 1.00 | root | | CARTESIAN inner join |
| ├─StreamAgg_45(Build) | 1.00 | root | | funcs:min(test.t.a)->Column#5 |
| │ └─Limit_49 | 1.00 | root | | offset:0, count:1 |
| │ └─IndexReader_59 | 1.00 | root | | index:Limit_58 |
| │ └─Limit_58 | 1.00 | cop[tikv] | | offset:0, count:1 |
| │ └─IndexFullScan_57 | 1.00 | cop[tikv] | table:t, index:idx_a(a) | keep order:true, stats:pseudo |
| └─StreamAgg_24(Probe) | 1.00 | root | | funcs:max(test.t.a)->Column#4 |
| └─Limit_28 | 1.00 | root | | offset:0, count:1 |
| └─IndexReader_38 | 1.00 | root | | index:Limit_37 |
| └─Limit_37 | 1.00 | cop[tikv] | | offset:0, count:1 |
| └─IndexFullScan_36 | 1.00 | cop[tikv] | table:t, index:idx_a(a) | keep order:true, desc, stats:pseudo |
+------------------------------------+---------+-----------+-------------------------+-------------------------------------+
12 rows in set (0.01 sec)