パーティションの剪定
パーティションプルーニングは、パーティションテーブルに適用されるパフォーマンスの最適化です。クエリステートメントのフィルタ条件を分析し、必要なデータが含まれていない場合は、パーティションを考慮から除外(プルーニング)します。 TiDBは、不要なパーティションを排除することで、アクセスする必要のあるデータの量を減らし、クエリの実行時間を大幅に改善できる可能性があります。
次に例を示します。
CREATE TABLE t1 (
id INT NOT NULL PRIMARY KEY,
pad VARCHAR(100)
)
PARTITION BY RANGE COLUMNS(id) (
PARTITION p0 VALUES LESS THAN (100),
PARTITION p1 VALUES LESS THAN (200),
PARTITION p2 VALUES LESS THAN (MAXVALUE)
);
INSERT INTO t1 VALUES (1, 'test1'),(101, 'test2'), (201, 'test3');
EXPLAIN SELECT * FROM t1 WHERE id BETWEEN 80 AND 120;
+----------------------------+---------+-----------+------------------------+------------------------------------------------+
| id | estRows | task | access object | operator info |
+----------------------------+---------+-----------+------------------------+------------------------------------------------+
| PartitionUnion_8 | 80.00 | root | | |
| ├─TableReader_10 | 40.00 | root | | data:TableRangeScan_9 |
| │ └─TableRangeScan_9 | 40.00 | cop[tikv] | table:t1, partition:p0 | range:[80,120], keep order:false, stats:pseudo |
| └─TableReader_12 | 40.00 | root | | data:TableRangeScan_11 |
| └─TableRangeScan_11 | 40.00 | cop[tikv] | table:t1, partition:p1 | range:[80,120], keep order:false, stats:pseudo |
+----------------------------+---------+-----------+------------------------+------------------------------------------------+
5 rows in set (0.00 sec)
パーティションプルーニングの使用シナリオ
パーティションプルーニングの使用シナリオは、範囲パーティションテーブルとハッシュパーティションテーブルの2種類のパーティションテーブルで異なります。
ハッシュパーティションテーブルでパーティションプルーニングを使用する
このセクションでは、ハッシュパーティションテーブルでのパーティションプルーニングの適用可能な使用シナリオと適用できない使用シナリオについて説明します。
ハッシュパーティションテーブルで適用可能なシナリオ
パーティションプルーニングは、ハッシュパーティションテーブルの等価比較のクエリ条件にのみ適用されます。
create table t (x int) partition by hash(x) partitions 4;
explain select * from t where x = 1;
+-------------------------+----------+-----------+-----------------------+--------------------------------+
| id | estRows | task | access object | operator info |
+-------------------------+----------+-----------+-----------------------+--------------------------------+
| TableReader_8 | 10.00 | root | | data:Selection_7 |
| └─Selection_7 | 10.00 | cop[tikv] | | eq(test.t.x, 1) |
| └─TableFullScan_6 | 10000.00 | cop[tikv] | table:t, partition:p1 | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+-----------------------+--------------------------------+
上記のSQLステートメントでは、条件x = 1
から、すべての結果が1つのパーティションに分類されることがわかります。値1
は、ハッシュパーティションを通過した後、 p1
パーティションにあることを確認できます。したがって、 p1
のパーティションのみをスキャンする必要があり、一致する結果がp4
p2
p3
にアクセスする必要はありません。実行プランからは、 TableFullScan
演算子が1つだけ表示され、 access object
でp1
パーティションが指定されているため、 partition pruning
が有効になっていることが確認できます。
ハッシュパーティションテーブルの適用できないシナリオ
このセクションでは、ハッシュパーティションテーブルでのパーティションプルーニングの2つの適用できない使用シナリオについて説明します。
シナリオ1
クエリ結果が>
between
の<
( in
など)のみに<=
するという条件を確認できない場合は、パーティション>=
最適化を使用できません。例えば:
create table t (x int) partition by hash(x) partitions 4;
explain select * from t where x > 2;
+------------------------------+----------+-----------+-----------------------+--------------------------------+
| id | estRows | task | access object | operator info |
+------------------------------+----------+-----------+-----------------------+--------------------------------+
| Union_10 | 13333.33 | root | | |
| ├─TableReader_13 | 3333.33 | root | | data:Selection_12 |
| │ └─Selection_12 | 3333.33 | cop[tikv] | | gt(test.t.x, 2) |
| │ └─TableFullScan_11 | 10000.00 | cop[tikv] | table:t, partition:p0 | keep order:false, stats:pseudo |
| ├─TableReader_16 | 3333.33 | root | | data:Selection_15 |
| │ └─Selection_15 | 3333.33 | cop[tikv] | | gt(test.t.x, 2) |
| │ └─TableFullScan_14 | 10000.00 | cop[tikv] | table:t, partition:p1 | keep order:false, stats:pseudo |
| ├─TableReader_19 | 3333.33 | root | | data:Selection_18 |
| │ └─Selection_18 | 3333.33 | cop[tikv] | | gt(test.t.x, 2) |
| │ └─TableFullScan_17 | 10000.00 | cop[tikv] | table:t, partition:p2 | keep order:false, stats:pseudo |
| └─TableReader_22 | 3333.33 | root | | data:Selection_21 |
| └─Selection_21 | 3333.33 | cop[tikv] | | gt(test.t.x, 2) |
| └─TableFullScan_20 | 10000.00 | cop[tikv] | table:t, partition:p3 | keep order:false, stats:pseudo |
+------------------------------+----------+-----------+-----------------------+--------------------------------+
この場合、対応するハッシュパーティションはx > 2
条件では確認できないため、パーティションプルーニングは適用できません。
シナリオ2
パーティションプルーニングのルール最適化はクエリプランの生成フェーズで実行されるため、パーティションプルーニングは、実行フェーズでのみフィルター条件を取得できるシナリオには適していません。例えば:
create table t (x int) partition by hash(x) partitions 4;
explain select * from t2 where x = (select * from t1 where t2.x = t1.x and t2.x < 2);
+--------------------------------------+----------+-----------+------------------------+----------------------------------------------+
| id | estRows | task | access object | operator info |
+--------------------------------------+----------+-----------+------------------------+----------------------------------------------+
| Projection_13 | 9990.00 | root | | test.t2.x |
| └─Apply_15 | 9990.00 | root | | inner join, equal:[eq(test.t2.x, test.t1.x)] |
| ├─TableReader_18(Build) | 9990.00 | root | | data:Selection_17 |
| │ └─Selection_17 | 9990.00 | cop[tikv] | | not(isnull(test.t2.x)) |
| │ └─TableFullScan_16 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo |
| └─Selection_19(Probe) | 0.80 | root | | not(isnull(test.t1.x)) |
| └─MaxOneRow_20 | 1.00 | root | | |
| └─Union_21 | 2.00 | root | | |
| ├─TableReader_24 | 2.00 | root | | data:Selection_23 |
| │ └─Selection_23 | 2.00 | cop[tikv] | | eq(test.t2.x, test.t1.x), lt(test.t2.x, 2) |
| │ └─TableFullScan_22 | 2500.00 | cop[tikv] | table:t1, partition:p0 | keep order:false, stats:pseudo |
| └─TableReader_27 | 2.00 | root | | data:Selection_26 |
| └─Selection_26 | 2.00 | cop[tikv] | | eq(test.t2.x, test.t1.x), lt(test.t2.x, 2) |
| └─TableFullScan_25 | 2500.00 | cop[tikv] | table:t1, partition:p1 | keep order:false, stats:pseudo |
+--------------------------------------+----------+-----------+------------------------+----------------------------------------------+
このクエリはt2
から行を読み取るたびに、 t1
のパーティションテーブルに対してクエリを実行します。理論的には、この時点でt1.x = val
のフィルター条件が満たされますが、実際には、パーティションプルーニングは、実行フェーズではなく、クエリプランの生成フェーズでのみ有効になります。
Rangeパーティション表でパーティションプルーニングを使用する
このセクションでは、範囲パーティション表でのパーティションプルーニングの適用可能な使用シナリオと適用できない使用シナリオについて説明します。
Rangeパーティション表の適用可能なシナリオ
このセクションでは、範囲パーティション表でのパーティションプルーニングの3つの適用可能な使用シナリオについて説明します。
シナリオ1
パーティションプルーニングは、範囲パーティションテーブルの等価比較のクエリ条件に適用されます。例えば:
create table t (x int) partition by range (x) (
partition p0 values less than (5),
partition p1 values less than (10),
partition p2 values less than (15)
);
explain select * from t where x = 3;
+-------------------------+----------+-----------+-----------------------+--------------------------------+
| id | estRows | task | access object | operator info |
+-------------------------+----------+-----------+-----------------------+--------------------------------+
| TableReader_8 | 10.00 | root | | data:Selection_7 |
| └─Selection_7 | 10.00 | cop[tikv] | | eq(test.t.x, 3) |
| └─TableFullScan_6 | 10000.00 | cop[tikv] | table:t, partition:p0 | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+-----------------------+--------------------------------+
パーティションプルーニングは、 in
クエリ条件を使用する等式比較にも適用されます。例えば:
create table t (x int) partition by range (x) (
partition p0 values less than (5),
partition p1 values less than (10),
partition p2 values less than (15)
);
explain select * from t where x in(1,13);
+-----------------------------+----------+-----------+-----------------------+--------------------------------+
| id | estRows | task | access object | operator info |
+-----------------------------+----------+-----------+-----------------------+--------------------------------+
| Union_8 | 40.00 | root | | |
| ├─TableReader_11 | 20.00 | root | | data:Selection_10 |
| │ └─Selection_10 | 20.00 | cop[tikv] | | in(test.t.x, 1, 13) |
| │ └─TableFullScan_9 | 10000.00 | cop[tikv] | table:t, partition:p0 | keep order:false, stats:pseudo |
| └─TableReader_14 | 20.00 | root | | data:Selection_13 |
| └─Selection_13 | 20.00 | cop[tikv] | | in(test.t.x, 1, 13) |
| └─TableFullScan_12 | 10000.00 | cop[tikv] | table:t, partition:p2 | keep order:false, stats:pseudo |
+-----------------------------+----------+-----------+-----------------------+--------------------------------+
上記のSQLステートメントでは、 x in(1,13)
の条件から、すべての結果がいくつかのパーティションに分類されることがわかります。分析の結果、 x = 1
のすべてのレコードがp0
のパーティションにあり、 x = 13
のすべてのレコードがp2
のパーティションにあることがわかりました。したがって、アクセスする必要があるのはp0
とp2
のパーティションだけです。
シナリオ2
>=
<
は、 between
などの=
比較の<=
条件に適用され>
。例えば:
create table t (x int) partition by range (x) (
partition p0 values less than (5),
partition p1 values less than (10),
partition p2 values less than (15)
);
explain select * from t where x between 7 and 14;
+-----------------------------+----------+-----------+-----------------------+-----------------------------------+
| id | estRows | task | access object | operator info |
+-----------------------------+----------+-----------+-----------------------+-----------------------------------+
| Union_8 | 500.00 | root | | |
| ├─TableReader_11 | 250.00 | root | | data:Selection_10 |
| │ └─Selection_10 | 250.00 | cop[tikv] | | ge(test.t.x, 7), le(test.t.x, 14) |
| │ └─TableFullScan_9 | 10000.00 | cop[tikv] | table:t, partition:p1 | keep order:false, stats:pseudo |
| └─TableReader_14 | 250.00 | root | | data:Selection_13 |
| └─Selection_13 | 250.00 | cop[tikv] | | ge(test.t.x, 7), le(test.t.x, 14) |
| └─TableFullScan_12 | 10000.00 | cop[tikv] | table:t, partition:p2 | keep order:false, stats:pseudo |
+-----------------------------+----------+-----------+-----------------------+-----------------------------------+
シナリオ3
パーティションプルーニングは、パーティション式がfn(col)
の単純な形式であり、クエリ条件が>
、および<
の<=
fn
=
が単調であるシナリオに適用され>=
。
fn
関数が単調である場合、任意のx
とy
について、 x > y
の場合、 fn(x) > fn(y)
。そうすれば、このfn
の関数は厳密に単調と呼ぶことができます。 x
とy
の場合、 x > y
の場合、 fn(x) >= fn(y)
。この場合、 fn
は「単調」とも呼ばれます。理論的には、厳密にあるかどうかにかかわらず、すべての単調な関数はパーティションプルーニングによってサポートされます。現在、TiDBは次の単調な機能のみをサポートしています。
unix_timestamp
to_days
たとえば、パーティションの剪定は、パーティション式がfn(col)
の形式である場合に有効になります。ここで、 fn
は単調関数to_days
です。
create table t (id datetime) partition by range (to_days(id)) (
partition p0 values less than (to_days('2020-04-01')),
partition p1 values less than (to_days('2020-05-01')));
explain select * from t where id > '2020-04-18';
+-------------------------+----------+-----------+-----------------------+-------------------------------------------+
| id | estRows | task | access object | operator info |
+-------------------------+----------+-----------+-----------------------+-------------------------------------------+
| TableReader_8 | 3333.33 | root | | data:Selection_7 |
| └─Selection_7 | 3333.33 | cop[tikv] | | gt(test.t.id, 2020-04-18 00:00:00.000000) |
| └─TableFullScan_6 | 10000.00 | cop[tikv] | table:t, partition:p1 | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+-----------------------+-------------------------------------------+
Rangeパーティション表の適用できないシナリオ
パーティションプルーニングのルール最適化はクエリプランの生成フェーズで実行されるため、パーティションプルーニングは、実行フェーズでのみフィルター条件を取得できるシナリオには適していません。例えば:
create table t1 (x int) partition by range (x) (
partition p0 values less than (5),
partition p1 values less than (10));
create table t2 (x int);
explain select * from t2 where x < (select * from t1 where t2.x < t1.x and t2.x < 2);
+--------------------------------------+----------+-----------+------------------------+-----------------------------------------------------------+
| id | estRows | task | access object | operator info |
+--------------------------------------+----------+-----------+------------------------+-----------------------------------------------------------+
| Projection_13 | 9990.00 | root | | test.t2.x |
| └─Apply_15 | 9990.00 | root | | CARTESIAN inner join, other cond:lt(test.t2.x, test.t1.x) |
| ├─TableReader_18(Build) | 9990.00 | root | | data:Selection_17 |
| │ └─Selection_17 | 9990.00 | cop[tikv] | | not(isnull(test.t2.x)) |
| │ └─TableFullScan_16 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo |
| └─Selection_19(Probe) | 0.80 | root | | not(isnull(test.t1.x)) |
| └─MaxOneRow_20 | 1.00 | root | | |
| └─Union_21 | 2.00 | root | | |
| ├─TableReader_24 | 2.00 | root | | data:Selection_23 |
| │ └─Selection_23 | 2.00 | cop[tikv] | | lt(test.t2.x, 2), lt(test.t2.x, test.t1.x) |
| │ └─TableFullScan_22 | 2.50 | cop[tikv] | table:t1, partition:p0 | keep order:false, stats:pseudo |
| └─TableReader_27 | 2.00 | root | | data:Selection_26 |
| └─Selection_26 | 2.00 | cop[tikv] | | lt(test.t2.x, 2), lt(test.t2.x, test.t1.x) |
| └─TableFullScan_25 | 2.50 | cop[tikv] | table:t1, partition:p1 | keep order:false, stats:pseudo |
+--------------------------------------+----------+-----------+------------------------+-----------------------------------------------------------+
14 rows in set (0.00 sec)
このクエリはt2
から行を読み取るたびに、 t1
のパーティションテーブルに対してクエリを実行します。理論的には、この時点でt1.x> val
のフィルター条件が満たされますが、実際には、パーティションプルーニングは、実行フェーズではなく、クエリプランの生成フェーズでのみ有効になります。