🐙

TiDB のパーティショニング その1:範囲、範囲列、範囲インターバル

2025/03/06に公開

今日はTiDBのパーティションニング機能を見ていきます。(この記事は2025年3月6日の情報に基づいています。TiDBは進化が早いので本格的な検証については、公式ドキュメントを見ることをお勧めします。)

そもそもパーティショニングとは

TiDBはMySQL互換でありパーティショニングも同様にサポートしています。とはいえ分散データベースという異なるアークテクチャにより高速性とスケーラビリティを実現しているため、方言のような機能差異も存在しているのでこの記事ではそれを見ていきます。

まずパーティショニングとは、大きなテーブルを小さいテーブルに分割して管理することでパフォーマンスやメンテナンス性を向上させる仕組みのことを言います。テーブルの分け方にはもちろんいろいろな方法があるのですが、第一回では範囲(Range)分割を見ていきます。

さっそくやってみる

範囲(Range) パーティション

まずは以下のSQLを実行します。

CREATE TABLE orders (
 id INT NOT NULL AUTO_INCREMENT,
    order_date DATE NOT NULL,
  PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024)
);

テーブルにプライマリーキーが設定されている場合、RANGE PARTITIONで設定する対象(上記の例だとorder_date)は必ずPRIMARY KEYでなければなりません。一方プライマリーキーがない場合は、そのままパーティションが作成可能です。

次に以下のINSERTを実行します。

INSERT INTO orders (order_date) VALUES ('2022-05-10');
INSERT INTO orders (order_date) VALUES ('2023-07-20');
INSERT INTO orders (order_date) VALUES ('2024-08-11');
INSERT INTO orders (order_date) VALUES ('2025-12-24');

上2行は実行されますが、下2行は以下のエラーとなります。

2024より大きいorder_dateを格納するパーティションが存在しないためです。

use test;

drop table orders;

CREATE TABLE orders (
 id INT NOT NULL AUTO_INCREMENT,
    order_date DATE NOT NULL,
  PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

再度データを操作します。

INSERT INTO orders (order_date) VALUES ('2022-05-10');
INSERT INTO orders (order_date) VALUES ('2023-07-20');
INSERT INTO orders (order_date) VALUES ('2024-08-11');
INSERT INTO orders (order_date) VALUES ('2025-12-24');

SELECT * FROM orders PARTITION (p2022);  -- 2022年のデータ確認
SELECT * FROM orders PARTITION (p2023);  -- 2023年のデータ確認
SELECT * FROM orders WHERE order_date = '2022-05-10';

無事SQLが実行されました。SELECT * FROM orders WHERE order_date = '2022-05-10';もTiDBのパーティション機能において重要なポイントです。通常MySQLであればパーティションによるクエリパフォーマンスの高速化を行うSQLの実行は以下の通りパーティションを明示的に指定する必要があります。

SELECT * FROM orders PARTITION (p2022) WHERE order_date = '2022-05-10';

しかしながら**TiDBの場合、明示的に指定しなくてもクエリオプティマイザが自動でパーティションを利用します。**これはSQLを書く際に便利ですね。

また以下の通り作成済の非パーティション化テーブルに対して、パーティション化を行い複数パーティションを作成することは可能です。

CREATE TABLE orders (
 id INT NOT NULL AUTO_INCREMENT,
    order_date DATE NOT NULL,
  PRIMARY KEY (id, order_date)
);

ALTER TABLE orders
PARTITION BY RANGE (order_date) (
    PARTITION p0 VALUES LESS THAN (2021-01-01),
    PARTITION p1 VALUES LESS THAN (2022-01-01)
);

ANALYZE TABLE orders;

以下のようにパーティション単位でのDROPも可能です。

ALTER TABLE orders DROP PARTITION p0;

この操作は

DELETE FROM orders WHERE order_date <= 2021-01-01;

を実行するよりも高速かつクラスターへの負荷を減らした処理が可能です。

範囲列パーティション分割

範囲列(Range Column)は範囲(Range)パーティションの派生です。Rangeパーティションが対象を整数や日付とし、パーティション作成時にPARTITION BY RANGE (YEAR(order_date)) と関数を指定できるのに対して、Range Columnパーティションは文字列(charvarchar)も含めることが可能です。また複数のColumnをパーティション条件として含めることができます。その一方Rangeパーティションと異なり関数は指定できません。
例えば以下のSQLを実行します。

CREATE TABLE customers (
  customer_id INT,
  customer_name VARCHAR(255)
)
PARTITION BY RANGE COLUMNS (customer_name) (
  PARTITION p0 VALUES LESS THAN ('M'),
  PARTITION p1 VALUES LESS THAN ('T'),
  PARTITION p2 VALUES LESS THAN ('Z')
);

これによりテーブルが3分割されます。これはあくまでシンプルな例で、実際は人の名前はかなり偏ったアルファベット分布をするので、おそらく上記の例だとデータは偏りますので、あくまで一例です。
以下のように複数カラム条件を指定することも可能です。

CREATE TABLE orders (
  order_id INT,
  order_date DATE,
  customer_id INT
)
PARTITION BY RANGE COLUMNS (order_date, customer_id) (
  PARTITION p0 VALUES LESS THAN ('2020-01-01', 1000),
  PARTITION p1 VALUES LESS THAN ('2020-01-01', 2000),
  PARTITION p2 VALUES LESS THAN ('2021-01-01', 1000)
);

指定された複数カラムはAND条件です。

範囲インターバルパーティション

範囲インターバルパーティションは、範囲パーティションと同じものを作成することがしますが、作成におけるSQLを簡素化させます。
例えば以下のSQLを見てみます。

CREATE TABLE rangeintervalsapmple (
  order_id INT,
  order_date DATE
)
PARTITION BY RANGE (order_id)
INTERVAL (100) FIRST PARTITION LESS THAN (100) LAST PARTITION LESS THAN (10000) MAXVALUE PARTITION

これは以下のSQLと同じ意味を持ちます。

CREATE TABLE rangeintervalsapmple (
  order_id INT,
  order_date DATE
)
PARTITION BY RANGE (order_id)
(PARTITION `P_LT_100` VALUES LESS THAN (100),
 PARTITION `P_LT_200` VALUES LESS THAN (200),
<snip>
 PARTITION `P_LT_9900` VALUES LESS THAN (9900),
 PARTITION `P_LT_10000` VALUES LESS THAN (10000),
 PARTITION `P_MAXVALUE` VALUES LESS THAN (MAXVALUE))

つまりorder_idを100個単位でパーティションを作成したい場合、その指定は膨大な行になりますが、自動で指定された間隔(上の例でいえば100)単位でパーティションを作成します。
以下のようにインターバルはも指定できます。

CREATE TABLE monthly_report_status (
    report_id int NOT NULL,
    report_status varchar(20) NOT NULL,
    report_date date NOT NULL
)
PARTITION BY RANGE COLUMNS (report_date)
INTERVAL (1 MONTH) FIRST PARTITION LESS THAN ('2000-01-01') LAST PARTITION LESS THAN ('2025-01-01');

また通常のRangeパーティションでも同様ですがこのような定期的な間隔をもって作成されたパーティションの場合

ALTER TABLE monthly_report_status FIRST PARTITION LESS THAN ('2010-01-01');

このようにオンライン状態のままパーティションの変更可能です。この例だと2000-01-01から2010-01-01までのパーティションをすべて削除し改めて、一つのパーティションに作成しなおします。つまりパーティションの統合が可能となっています。

Discussion