👨‍💻

MYSQLのパーティショニングについて

2025/01/02に公開

MYSQLのパーティショニングについて

パーティショニングとは

大規模なデータ(ログデータ、ユーザーデータなど)を取得するときに、効率的に管理して、高速に取得する仕組み。
テーブルをパーティション化することで指定したカラムを基準にテーブルを分割して保存し、クエリ検索時に関連するパーティションだけ取得すれば良くなります。

パーティションは実際には物理的な複数のテーブルを作るわけではなく、データを内部的に複数に分割する仕組みとなっており、ユーザから見たときに1つのテーブルとして見えるが、内部的には複数の条件でデータが分割され保存されています。

パーティション化前後のテーブル比較

パーティション前(元のテーブル)

パーティション後

内部的には条件によって分けて保存されてます。

パーティショニングの種類

パーティショニングには複数の種類があります。今回は以下3つのパーティショニングについて説明します。

  • RANGEパーティショニング
  • LISTパーティショニング
  • HASHパーティショニング

RANGEパーティショニング

特定の範囲(例:期間・数値)を基準にデータを分割する方法。

例えば、ログデータを年度ごとに管理したい場合、created_at(作成日時)カラムを条件にパーティションを分割することで、特定の年度に該当するデータのみを効率的に取得することができます。

基本書式

PARTITION BY RANGE (<条件>) (
	PARTITION <名前> VALUES LESS THAN (<>)
)

例:ログデータを年度ごとのパーティションに分割する場合

CREATE TABLE logs
	id INT NOT NULL,
	created_at DATE NOT NULL
	log_message TEXT,
	PRIMARY KEY (id, created_at
)
PARTITION BY RANGE (YEAR(created_at)) (
	PARTITION p2022 VALUES LESS THAN (2023),--2022のデータ
	PARTITION p2023 VALUES LESS THAN (2024), --2023のデータ
	PARTITION p_max VALUES LESS THAN MAXVALUE,
)

※MAXVALUEは、MySQLのRANGEパーティションで使用される特殊な値で、任意の値を受け入れることが可能です。例えば、2024年のデータが必要になった場合、この設定ではp_maxに対応します。ただし、年は毎年増え続けるものなので、将来的に新しい年度を追加する際には、ALTER TABLEステートメントを使用してパーティションを追加する必要があります。

RANGEパーティショニングが役立つケース:

  1. 古いデータを効率的に削除したいとき。
  2. 日付や時間に基づく検索が多いとき。
  3. パーティションされたカラムを利用したクエリを頻繁に実行するとき。

LISTパーティショニング

特定のカラムの値を基準にデータを分割するパーティション方式。

RANGEパーティショニングが「値の範囲」でデータを分割するのに対して、LISTパーティショニングは特定の値に基づいてデータを分割します。
値があらかじめ決まっているカテゴリやグループでデータを分ける場合に適している。

基本書式

PARTITION BY LIST (<条件>) (
	PARTITION <名前> VALUES IN (<1>, <2>, ...)
);

例:地域ごとにパーティションで分割する場合

CREATE TABLE customers (
	id INT NOT NULL,
	region VARCHAR(10),
	name VARCHAR(50),
	PRIMARY KEY (id, region)
)
PARTITION BY LIST (region) (
	PARTITION p_north VALUES IN ('North'),
	PARTITION p_south VALUES IN ('South'),
	PARTITION p_west VALUES IN ('West'),
	PARTITION p_east VALUES IN ('East')
)

※1:MAXVALUE などの「すべての状況に対応する」ものは。パーティション式で予期されるすべての値を PARTITION ... VALUES IN (...) 句で指定する必要があります。
※2:MySQL 8.0 では、LIST COLUMNS パーティション化もサポートされています。整数型以外のカラムを使用したり、複数のカラムをパーティション化のキーにすることができます。

LISTパーティショニングが役立つケース:

  1. 特定のカテゴリやグループに基づいてデータを分割したいとき
  2. 固定値に基づいた検索が多いとき
  3. 範囲ではなくリストで分割したいとき

HASHパーティショニング

どのデータがどのパーティションに入るかを決めるために、指定したカラムの値や式をハッシュ関数で処理します。その結果からデータを指定したパーティションに均等に割り当てます。

基本書式

PARTITION BY HASH (<条件>)
PARTITIONS <パーティション数>;

例:ユーザーIDを基準に均等に分割

CREATE TABLE user_logs (
	id INT NOT NULL,
	user_id INT NOT NULL,
	log TEXT,
	PRIMARY KEY (id, user_id)
)
PARTITION BY HASH(user_id)
PARTITIONS 4;

HASHパーティションが役立つケース:

  1. ランダムアクセスが多い場合
SELECT * FROM session_logs WHERE user_id = 12345;

このクエリだと該当するパーティションを1つだけスキャンすればいいので効率が良くなります。

感想

パーティション機能は用途によって非常に魅力的に見える一方で、実際には扱いが難しい側面もあると感じました。

例えば、LISTパーティショニングやRANGEパーティショニングでは、項目や条件が増えるたびに手動でパーティションを追加する必要があります。これにより、大規模なシステム(例: 年度やカテゴリが頻繁に追加されるシステム)では、管理コストが大幅に増加する可能性があります。このような運用負担を考慮しないと、後々のメンテナンスが非常に手間になる懸念があります。

また、Qiitaの記事にもあるように、パーティションキーに含まれない条件で絞り込むクエリでは、パーティションプルーニングが効かず、全パーティションをスキャン(フルスキャン)してしまう場合があります。これでは、パフォーマンス向上のために導入したパーティションが、かえってパフォーマンスを悪化させる結果になりかねません。

個人的に使うケースは結構限られそうかなと思うので、インデックス追加やクエリ効率化、テーブル設計の最適化を先に行うのがいいのかなと思います。

参考資料

https://dev.mysql.com/doc/refman/8.0/ja/partitioning-types.html
https://qiita.com/TokyoYoshida/items/83ae76be260b2f54f80a
https://qiita.com/hmatsu47/items/354f979cde6ad91bcc6b

Discussion