最大/最小を排除

SQL 文にmax min関数が含まれている場合、クエリ オプティマイザはmax min化ルールを適用してmax minの集計関数をTopN 演算子に変換しようとします。このようにして、TiDB はインデックスを通じてクエリをより効率的に実行できます。

この最適化ルールはmin selectステートメント内のmax関数の数に応じて次の 2 つのタイプに分けられます。

max / min機能1つ

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

書き換えにより、オプティマイザはmax関数min 1 つだけ持つステートメントのルールを 2 つのサブクエリにそれぞれ適用できます。ステートメントは次のように書き換えられます。

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)

このページは役に立ちましたか?