MySQL | SQLite経験者向け、30日で習得するMySQL:パフォーマンスと設計 - Day21 パーティショニング

SQL MySQL
スポンサーリンク

Day21 後半のゴール

「“どんな軸でどう分割するか”を、自分で設計できるようになる」

前半で、「パーティショニングは大規模データ専用の武器」「日付で分割すると便利」というところまでイメージを掴みました。
後半では、もう一歩踏み込んで、

どのカラムをパーティションキーに選ぶか
MySQL でどう定義するのか(イメージレベルで)
やってはいけない分割の仕方・ハマりポイント

ここを押さえて、「このテーブルならこう分割する」と自分で判断できる状態を目指します。


パーティションキーの選び方

「“よく範囲検索に使うカラム”かつ“データが自然に偏るカラム”」

パーティショニングで一番大事なのは、「どのカラムで分割するか」です。
このカラムを「パーティションキー」と呼びます。

感覚としては、次の二つを満たすカラムが向いています。

よく範囲検索に使うカラム
時間とともに自然に増えていく、または偏りがあるカラム

典型例が created_at や event_date です。
ログや履歴は、ほぼ必ず「いつのデータか」で検索しますし、
時間が経つほど古いデータは参照頻度が下がるので、「古いパーティションを捨てる」という運用とも相性が良いです。

逆に、user_id のような「ランダムに散るID」は、パーティションキーとしてはあまり向きません。
user_id でパーティションを分けても、

特定ユーザーのデータだけを見たいときにしか効かない
日付で範囲検索するときに、全パーティションをまたぐことになる

といった形で、「大規模データの分割」という目的と噛み合いにくくなります。

パーティションキーを選ぶときは、

このテーブルに対して、どんな WHERE 条件が一番多いか
古いデータをまとめて捨てたい軸は何か

を、クエリと運用の両方から考えるのがポイントです。


例題:アクセスログを月ごとにパーティショニングする

「“SQLは普通のテーブルと同じ”を体で感じる」

具体的なイメージを持つために、アクセスログを月ごとに分割する例を見てみます。
コードは「こういう感じか」と眺めるだけで大丈夫です。

パーティションありのテーブル定義のイメージ

例えば、こんなテーブルを作るとします。

CREATE TABLE access_logs (
  id         BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id    INT,
  path       VARCHAR(255),
  created_at DATETIME NOT NULL
)
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
  PARTITION p202401 VALUES LESS THAN (202402),
  PARTITION p202402 VALUES LESS THAN (202403),
  PARTITION p202403 VALUES LESS THAN (202404),
  PARTITION pmax    VALUES LESS THAN MAXVALUE
);
Python

ここでやっていることを日本語にすると、

created_at から「YYYYMM」の数値を作って
その値の範囲ごとにパーティションを分ける

ということです。

p202401 には 2024年1月のデータ
p202402 には 2024年2月のデータ
p202403 には 2024年3月のデータ
それ以外は pmax

というイメージです。

重要なのは、アプリからの SELECT は普通のテーブルと同じだということです。

SELECT *
FROM access_logs
WHERE created_at BETWEEN '2024-02-01' AND '2024-02-29';
Python

このクエリを書いたとき、MySQL が内部で、

「2024年2月のパーティション p202402 だけ見ればいい」

と判断してくれます。
これが「パーティション・プルーニング」です。


パーティションの追加と削除

「“ALTER TABLE で引き出しを増やしたり捨てたりする”感覚」

パーティショニングを使うときに、運用で必ず出てくるのが「パーティションの追加と削除」です。

ログが毎月増えていくなら、
新しい月のパーティションを追加する必要があります。

例えば、2024年4月のパーティションを追加したいときは、イメージとしてこうです。

ALTER TABLE access_logs
ADD PARTITION (
  PARTITION p202404 VALUES LESS THAN (202405)
);
Python

逆に、「2023年分のログはもういらないから捨てたい」というときは、
該当パーティションを DROP します。

ALTER TABLE access_logs
DROP PARTITION p202301;
Python

DELETE で何百万行も消すのではなく、
パーティションごと「引き出しを捨てる」イメージです。

ここでの重要ポイントは、

パーティショニングを使うと、「古いデータの削除」が運用メニューになる

ということです。
ログ系テーブルでは、このメリットが本当に大きいです。


パーティショニングの注意点

「“とりあえず使う”と痛い目を見るポイント」

パーティショニングは強力ですが、クセもあります。
代表的な注意点を、初心者目線で押さえておきます。

パーティションキーを含まない WHERE では、あまり速くならない

例えば、created_at でパーティショニングしている access_logs に対して、

SELECT *
FROM access_logs
WHERE user_id = 123;
Python

のように、created_at を一切使わないクエリを投げると、
MySQL は「どのパーティションにあるか分からない」ので、
全パーティションをまたいで検索することになります。

つまり、

パーティショニングの恩恵を受けるには、
クエリ側でもパーティションキーを WHERE に含めるのが基本

ということです。

「user_id だけで検索するクエリが多いテーブル」を created_at でパーティショニングしても、
あまり嬉しくない、というイメージを持っておいてください。

パーティション数を増やしすぎると逆効果になる

「細かく分けた方が速そう」と思って、
1日ごと、あるいは1時間ごとにパーティションを切る、という設計もできます。

しかし、パーティションが増えすぎると、

管理が大変になる
オプティマイザのコストが増える
メタデータの扱いが重くなる

といったデメリットが出てきます。

現場感覚としては、

月単位、せいぜい日単位くらいまでが現実的な落としどころ

ということが多いです。

「どれくらい細かく分けるか」は、

1パーティションあたりの行数
運用でどの粒度でデータを捨てたいか

を見ながら決めるのが良いです。


パーティショニングとインデックスの組み合わせ

「“パーティションの中”でもインデックスが効くようにする」

前半でも触れましたが、パーティショニングとインデックスは役割が違います。

パーティショニング
→ どのパーティションを見るかを減らす

インデックス
→ パーティションの中で、どの行を読むかを減らす

例えば、access_logs を created_at でパーティショニングしているとして、
よくあるクエリがこれだとします。

SELECT *
FROM access_logs
WHERE created_at BETWEEN '2025-01-01' AND '2025-01-31'
  AND user_id = 123;
Python

このとき、理想的な動きはこうです。

2025年1月のパーティションだけを見る(パーティショニングの効果)
そのパーティションの中で、user_id のインデックスを使って絞る

つまり、

パーティションキー(created_at)

よく絞り込みに使うカラム(user_id)

の両方を意識した設計が必要になります。

MySQL のパーティションインデックスには細かい仕様がありますが、
Day21 の段階では、

パーティショニングをしても、インデックス設計はサボれない
むしろ「パーティションキー+よく使うカラム」の組み合わせを意識する

という感覚だけ持っておけば十分です。


パーティショニングを導入するかどうかの判断フロー

「“最後の手段”ではなく“条件が揃ったら使う専用武器”」

最後に、「このテーブルにパーティショニングを使うべきか」を判断するための考え方をまとめます。

まず、テーブルの規模を見ます。
数十万〜数百万行程度なら、まずはインデックスとクエリチューニングで戦う。
数千万〜億行クラスになってきたら、パーティショニングを検討する。

次に、クエリのパターンを見ます。
日付やIDレンジなど、特定のカラムで範囲検索することが多いか。
そのカラムをパーティションキーにできるか。

さらに、運用を見ます。
古いデータを定期的に削除する運用があるか。
「1年分だけ残して、あとは捨てる」などのルールがあるか。

これらが揃っているなら、

このテーブルはパーティショニングの“適性あり”

と判断できます。

逆に、

行数がそこまで多くない
範囲検索よりも「主キー1件取得」がメイン
古いデータもずっと残す

というテーブルにパーティショニングを入れると、
複雑さだけ増えて、あまり得をしません。

パーティショニングは、

「なんでも速くする魔法」ではなく
「条件が揃ったときに刺さる専用武器」

として扱うのが、実務的なバランスです。


Day21 後半のまとめ

パーティショニングを実務で使うときの核心は、「どのカラムをパーティションキーにするか」と「そのテーブルが本当に“大規模データ専用の武器”を必要としているか」を見極めることであり、アクセスログのように行数が爆発しやすく、created_at で範囲検索することが多く、古いデータを月単位・年単位でまとめて捨てたいテーブルなら、created_at を使って月ごとに RANGE パーティションを切ることで「関係ない期間のパーティションをそもそも読まない」「古い月のパーティションを DROP して一瞬で削除する」といったメリットを享受できる。
一方で、パーティションキーを WHERE に含まないクエリでは全パーティションをまたいで検索することになり、細かく分割しすぎると管理やオプティマイザのコストが増えるなどのデメリットもあるため、「日付などでの範囲検索が多いか」「古いデータを定期的に捨てるか」「行数が数千万〜億行クラスか」といった条件が揃ったテーブルに絞って導入し、そのうえで「パーティションで“見るべきかたまり”を減らし、インデックスで“かたまりの中の行”を減らす」という役割分担を意識することが、パーティショニングを“怖い機能”ではなく“狙って使える武器”に変えていく鍵になる。

タイトルとURLをコピーしました