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

最大/最小を排除する

SQLステートメントにmax関数が含まれている場合、クエリminmin最適化ルールを適用して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)