MySQL8.0 RANGEパーティション パフォーマンス調査
やぁどうも。株式会社mikanのwadyです。
こちらは mikan Advent Calendar 2025 の 7日目の記事です。

6日目の記事は、宇波さんのハロプロ愛にあふれるアイドルソングと英語学習教材の共通項から見つけたヒットの法則 でした。
この記事では一転して、RDBMSのアイドルであるMySQLのRANGEパーティションのパフォーマンスについて調査した記事です。
背景
業務で、ユーザーの学習履歴を保存するテーブルに対して MySQL の RANGE パーティションを設定する機会がありました。
「レコード数が増えるテーブルにはパーティションを貼ると良い」という知識自体はなんとなく持っていたものの、
- 実際どれくらい速くなるのか?
- インデックスだけではダメなのか?
- どんなクエリパターンならパーティションの恩恵が大きいのか?
といったところを、正しく説明できる状態ではありませんでした。
ちょうどアドベントカレンダーでお鉢が回ってきたので、
パーティションの有無でどれくらい挙動が変わるのか?
パーティションなしの世界線を再現して比較してみよう
というテーマで、検証用に約 1.2 億行のダミーデータを用意し、普段よく書くクエリパターンごとに実行時間を計測してみました。
この記事は、
- MySQL テーブルがだんだん大きくなってきて不安になっている人
- 「パーティションって聞くけど、実際どこから必要なの?」と思っている人
向けの、自分用メモ兼ナレッジ共有です。
この記事で分かること
MySQL 8.0 の RANGE パーティション(created_at を月ごとに分割)を、約 1.2 億行のテーブルで検証した結果をまとめた記事です。
この記事では、次のようなことが分かります。
- 日付をキーにした RANGE パーティションが インデックスよりも速くなるクエリパターン
- 日付条件+大量データ(数万〜数百万行)を返すクエリで 2〜11倍 の高速化
- 逆に、インデックスだけで十分なクエリパターン
- ユーザー単位・特定日のような、結果セットが小さいクエリはほぼインデックス無双
- パーティションがあっても遅くなりがちなケース
- 全件 COUNT、日付条件なしの全ユーザー集計、大量 DELETE など
- 「いつパーティションを導入すべきか?」を考えるための判断フロー
- データ量、クエリパターン、古いデータの削除要件、PRIMARY KEY 制約など
自分のプロダクトで本当にパーティションが必要かどうか判断するための材料として読んでいただけるとありがたいです!
パーティションとインデックスとは
検証結果に入る前に、パーティションとインデックスについて簡単に整理しておきます。
パーティションとは
パーティションは、1つの論理的なテーブルを物理的に複数のファイルに分割して格納する仕組みです。
例えば、1.2億行のテーブルを月ごとに12分割すると、各パーティションは約1000万行になります。クエリ実行時に条件に合致するパーティションだけを読み込む(パーティションプルーニング)ことで、スキャン対象を大幅に削減できます。
インデックスとは
インデックスは、テーブル内のデータを高速に検索するための索引(目次) です。
B-Tree構造でデータへの参照を保持し、フルテーブルスキャンを避けて効率的にデータを取得できます。
パーティションとインデックスの違い
| 観点 | パーティション | インデックス |
|---|---|---|
| 仕組み | テーブルを物理的に分割 | データへの参照を索引として保持 |
| 効果が出る場面 | 大量データの絞り込み・削除 | 少量データのピンポイント検索 |
| スキャン削減の方法 | 不要なパーティションを読み飛ばす | B-Tree探索で目的のデータに直接到達 |
| 運用コスト | パーティション追加・管理が必要 | 作成後はほぼ自動 |
重要なポイント: パーティションとインデックスは排他的ではなく、併用することで最大の効果を発揮します。今回の検証でも、パーティション + インデックスの組み合わせが多くのケースで最速でした。
RANGE パーティションを選んだ理由
- 学習履歴なので年月単位で分けることが可能
- 学習開始日以降といった日付を指定したクエリが多い
- 古いデータの削除もしやすい(パーティションDROPで済む)
テーブル構造
以下のカラムを持つユーザの学習年月をキーにしたパーティションテーブルを作成しました。
-
id: レコードに付与された一位のID -
user_id: 学習したユーザID -
question_id: ユーザが学習した問題ID -
created_at: ユーザの学習日時
CREATE TABLE learning_history_partitioned (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
question_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at),
KEY idx_question_id (question_id),
KEY idx_created_at (created_at),
KEY idx_user_created (user_id, created_at),
KEY idx_user_question_id_created (user_id, question_id, created_at)
) ENGINE=InnoDB
PARTITION BY RANGE COLUMNS (created_at) (
PARTITION p202501 VALUES LESS THAN ('2025-02-01'),
PARTITION p202502 VALUES LESS THAN ('2025-03-01'),
...
PARTITION p202511 VALUES LESS THAN ('2025-12-01'),
PARTITION p202512 VALUES LESS THAN ('2026-01-01')
);
RANGEパーティションを作成するときのポイント:
- パーティションキーを PRIMARY KEY に含める必要がある
-
RANGE COLUMNSを使うことでDATE型をそのまま使える
シミュレーション方法
-
パーティションなし、インデックスなしのテーブルを作成
以下のようなインデックスを貼っていないピュアなテーブルを用意しlearning_historyと命名しました。
CREATE TABLE learning_history (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
question_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
-
パーティションなし、インデックスありのテーブルを作成
セカンダリインデックス(question_id、created_at)と複合インデックス(user_id, created_at、user_id, question_id, created_at)を設定したテーブルを用意し、learning_history_indexedと命名しました。
CREATE TABLE learning_history_indexed (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
question_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_question_id (question_id),
KEY idx_created_at (created_at),
KEY idx_user_created (user_id, created_at),
KEY idx_user_question_id_created (user_id, question_id, created_at)
) ENGINE=InnoDB;
-
本番想定に近いデータ量を投入
本番環境を想定し、3つのテーブルそれぞれに1ヶ月あたり1000万レコード、1年間で合計1.2億レコードのダミーデータを投入しました。(データの準備だけで丸3日かかりました...) -
よく使うクエリを実際に流して実行時間の比較
以下のような業務で頻繁に使用しそうなクエリを3つのテーブル上で実行し、実行時間を比較しました。- BETWEENを使った履歴取得
- 期間以降の大量データ取得
- 期間 + ユーザー別集計
- 問題 + 日付条件
- JOIN + 日付条件
- 全件 COUNT
- 期間を指定しての削除
- LIMITを設定したDELETE
- 日付条件なしの全ユーザー集計
パフォーマンス比較
検証結果を以下の3パターンのクエリに分けて紹介します。
- パーティションが最速になるクエリ
- 注意が必要なクエリ
- インデックスがパーティションより速いクエリ
パーティションが最速になるクエリ
BETWEENを使った履歴取得
SELECT *
FROM learning_history
WHERE created_at BETWEEN '2025-03-24' AND '2025-03-31';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: (learning_history.created_at between '2025-03-24' and '2025-03-31') (cost=11.5e+6 rows=12.3e+6) (actual time=4538..27872 rows=2.26e+6 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=2.08..16181 rows=120e+6 loops=1)
EXPLAIN: -> Index range scan on learning_history_indexed using idx_created_at over ('2025-03-24 00:00:00' <= created_at <= '2025-03-31 00:00:00'), with index condition: (learning_history_indexed.created_at between '2025-03-24' and '2025-03-31') (cost=4.89e+6 rows=4.08e+6) (actual time=40.4..29938 rows=2.26e+6 loops=1)
EXPLAIN: -> Filter: (learning_history_partitioned.created_at between '2025-03-24' and '2025-03-31') (cost=1.03e+6 rows=4.99e+6) (actual time=2.82..3855 rows=2.26e+6 loops=1)
-> Covering index scan on learning_history_partitioned using idx_user_question_id_created (cost=1.03e+6 rows=9.97e+6) (actual time=2.81..2694 rows=10e+6 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 27.99秒 |
| learning_history_indexed | 30.23秒 |
| learning_history_partitioned | 3.99秒 |
→ indexed より 7.6 倍速
なぜ速いのか?
-
パーティションプルーニング:
created_at BETWEEN '2025-03-24' AND '2025-03-31'の条件から、MySQL はp202503パーティションのみをスキャン対象と判断 - スキャン行数の削減: indexed は 1.2億行全体のインデックスを走査するが、partitioned は該当月の約1000万行のみ
- I/O削減: 巨大なインデックスファイルではなく、小さなパーティション単位のインデックスを読むためディスクI/Oが大幅減少
期間以降の大量データ取得
SELECT *
FROM learning_history
WHERE created_at >= '2025-12-01';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: (learning_history.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=11.5e+6 rows=37e+6) (actual time=17298..18920 rows=10e+6 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=9.5..16094 rows=120e+6 loops=1)
EXPLAIN: -> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=11.2e+6 rows=16.7e+6) (actual time=12.3..75991 rows=10e+6 loops=1)
-> Covering index scan on learning_history_indexed using idx_user_question_id_created (cost=11.2e+6 rows=109e+6) (actual time=12.3..72906 rows=120e+6 loops=1)
EXPLAIN: -> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=928121 rows=4.5e+6) (actual time=7.17..6093 rows=10e+6 loops=1)
-> Covering index scan on learning_history_partitioned using idx_user_question_id_created (cost=928121 rows=8.99e+6) (actual time=7.16..5735 rows=10e+6 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 19.43秒 |
| learning_history_indexed | 1分16.56秒 |
| learning_history_partitioned | 6.62秒 |
→ indexed より 11.6 倍速
なぜ速いのか?
-
パーティションプルーニング:
created_at >= '2025-12-01'で 12月のパーティション(p202512)のみスキャン - インデックスサイズの分散: indexed は 1.2億行分の巨大インデックスを持つが、partitioned は各パーティションに分割された小さなインデックス
- キャッシュ効率: 直近データが集中するパーティションはメモリに載りやすく、キャッシュヒット率が高い
期間 + ユーザー別集計
SELECT user_id, COUNT(*)
FROM learning_history
WHERE created_at >= "2025-07-01"
GROUP BY user_id
ORDER BY user_id;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Sort: learning_history.user_id (actual time=98248..98268 rows=1e+6 loops=1)
-> Table scan on <temporary> (actual time=98051..98139 rows=1e+6 loops=1)
-> Aggregate using temporary table (actual time=98051..98051 rows=999999 loops=1)
-> Filter: (learning_history.created_at >= TIMESTAMP'2025-07-01 00:00:00') (cost=11.5e+6 rows=37e+6) (actual time=8739..19147 rows=60e+6 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=5.57..15309 rows=120e+6 loops=1)
EXPLAIN: -> Group aggregate: count(0) (cost=16.7e+6 rows=1e+6) (actual time=11.3..65471 rows=1e+6 loops=1)
-> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-07-01 00:00:00') (cost=11.2e+6 rows=54.5e+6) (actual time=11.2..64097 rows=60e+6 loops=1)
-> Covering index scan on learning_history_indexed using idx_user_created (cost=11.2e+6 rows=109e+6) (actual time=11.2..60346 rows=120e+6 loops=1)
EXPLAIN: -> Group aggregate: count(0) (cost=9.01e+6 rows=3141) (actual time=3.25..32715 rows=1e+6 loops=1)
-> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-07-01 00:00:00') (cost=6.07e+6 rows=29.5e+6) (actual time=3.23..31288 rows=60e+6 loops=1)
-> Covering index scan on learning_history_partitioned using idx_user_created (cost=6.07e+6 rows=58.9e+6) (actual time=3.22..28922 rows=60e+6 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 1分38.31秒 |
| learning_history_indexed | 1分5.54秒 |
| learning_history_partitioned | 32.81秒 |
→ indexed より 2 倍速
なぜ速いのか?
-
パーティションプルーニング + 集計の効率化:
created_at >= '2025-07-01'で対象を半分(p202507〜p202512)に絞ってから GROUP BY -
cost の差に注目: indexed は
cost=16.7e+6だが partitioned はcost=9.01e+6と約半分 - Covering index scan の範囲縮小: パーティション単位でインデックスが分割されているため、スキャン対象のインデックスサイズ自体が小さい
問題 + 日付条件
SELECT * FROM learning_history
WHERE question_id = 50 AND created_at >= '2025-09-01';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: ((learning_history.question_id = 50) and (learning_history.created_at >= TIMESTAMP'2025-09-01 00:00:00')) (cost=11.5e+6 rows=3.7e+6) (actual time=11648..17542 rows=39649 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=9.43..14756 rows=120e+6 loops=1)
EXPLAIN: -> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-09-01 00:00:00') (cost=226817 rows=108008) (actual time=7426..10645 rows=40163 loops=1)
-> Index lookup on learning_history_indexed using idx_question_id (question_id=50) (cost=226817 rows=216016) (actual time=400..10641 rows=119894 loops=1)
EXPLAIN: -> Index lookup on learning_history_partitioned using idx_question_id (question_id=50), with index condition: (learning_history_partitioned.created_at >= TIMESTAMP'2025-09-01 00:00:00') (cost=74115 rows=70586) (actual time=420..3743 rows=39788 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 17.54秒 |
| learning_history_indexed | 10.67秒 |
| learning_history_partitioned | 3.80秒 |
→ indexed より 2.8 倍速
なぜ速いのか?
-
パーティションプルーニング:
created_at >= '2025-09-01'で対象を p202509〜p202512 の4パーティションに絞り込み -
cost の大幅削減: indexed は
cost=226817に対し partitioned はcost=74115と約 1/3 -
Index Condition Pushdown (ICP): partitioned では
with index condition:が表示されており、日付フィルタをインデックスレベルで処理- 今回の調査を通して初めて知った内容だが、パーティション化したInnoDBではICPで最適化され、本来複合インデックスであればその複合インデックスの順序通り指定してやる必要があるがICPが適用されている場合は
(question_id, created_at)でもインデックスが使用されるらしい。
https://dev.mysql.com/doc/refman/8.0/ja/index-condition-pushdown-optimization.html
- 今回の調査を通して初めて知った内容だが、パーティション化したInnoDBではICPで最適化され、本来複合インデックスであればその複合インデックスの順序通り指定してやる必要があるがICPが適用されている場合は
- スキャン行数の削減: indexed は 216,016行をスキャンするが、partitioned は 70,586行(約67%削減)
JOIN + 日付条件
SELECT lh.*, u.id
FROM learning_history lh
JOIN users u ON lh.user_id = u.id
WHERE lh.created_at >= '2025-12-01';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Nested loop inner join (cost=24.4e+6 rows=37e+6) (actual time=17105..30510 rows=10e+6 loops=1)
-> Filter: (lh.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=11.5e+6 rows=37e+6) (actual time=17105..18968 rows=10e+6 loops=1)
-> Table scan on lh (cost=11.5e+6 rows=111e+6) (actual time=5.93..16099 rows=120e+6 loops=1)
-> Single-row covering index lookup on u using PRIMARY (id=lh.user_id) (cost=0.25 rows=1) (actual time=0.00106..0.00108 rows=1 loops=10e+6)
EXPLAIN: -> Nested loop inner join (cost=11.4e+6 rows=15.2e+6) (actual time=3.96..79894 rows=10e+6 loops=1)
-> Covering index scan on u using PRIMARY (cost=100323 rows=998739) (actual time=1.72..121 rows=1e+6 loops=1)
-> Filter: (lh.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=1.35 rows=15.2) (actual time=0.06..0.0793 rows=10 loops=1e+6)
-> Covering index lookup on lh using idx_user_question_id_created (user_id=u.id) (cost=1.35 rows=99.2) (actual time=0.0591..0.0757 rows=120 loops=1e+6)
EXPLAIN: -> Nested loop inner join (cost=2.07e+6 rows=4.67e+6) (actual time=3.2..7921 rows=10e+6 loops=1)
-> Covering index scan on u using PRIMARY (cost=100323 rows=998739) (actual time=1.63..75 rows=1e+6 loops=1)
-> Filter: (lh.created_at >= TIMESTAMP'2025-12-01 00:00:00') (cost=1.04 rows=4.68) (actual time=0.00579..0.00745 rows=10 loops=1e+6)
-> Covering index lookup on lh using idx_user_question_id_created (user_id=u.id) (cost=1.04 rows=9.36) (actual time=0.0057..0.00698 rows=10 loops=1e+6)
| 環境 | 実行時間 |
|---|---|
| learning_history | 31.09秒 |
| learning_history_indexed | 1分20.64秒 |
| learning_history_partitioned | 8.56秒 |
→ indexed より 9 倍以上速
なぜ速いのか?
-
パーティションプルーニング + JOIN:
created_at >= '2025-12-01'で p202512 パーティションのみをJOINの対象に絞り込み -
cost の大幅削減: indexed は
cost=11.4e+6に対し partitioned はcost=2.07e+6と約 1/5 -
インデックスルックアップの効率化: partitioned では各ユーザーあたり
rows=9.36のみスキャンするが、indexed はrows=99.2と約10倍 - 駆動表の選択: indexed/partitioned では users を駆動表として選択し、各ユーザーごとに learning_history を検索する戦略を採用。パーティション内のインデックスが小さいため、ルックアップが高速
注意が必要なクエリ
全件 COUNT
SELECT COUNT(*) FROM learning_history;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Count rows in learning_history (actual time=2949..2949 rows=1 loops=1)
EXPLAIN: -> Count rows in learning_history_indexed (actual time=35275..35275 rows=1 loops=1)
EXPLAIN: -> Count rows in learning_history_partitioned (actual time=26114..26114 rows=1 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 2.95秒 |
| learning_history_indexed | 35.27秒 |
| learning_history_partitioned | 26.12秒 |
→ インデックスなしが最速。インデックスありは逆にオーバーヘッドになる特殊ケース。
なぜインデックスなしが速いと考えられるか?
- 走査対象がシンプル: インデックスなしテーブルではクラスタ化インデックス(PRIMARY KEY)だけを順に読みながら COUNT すればよく、セカンダリインデックスを複数持つテーブルより I/O が少ない
- インデックスのオーバーヘッド: indexed/partitioned はセカンダリインデックスを走査するため、インデックスページの読み込みが発生
- パーティションのオーバーヘッド: partitioned / indexed テーブルは、パーティションごと・インデックスごとのメタ情報を扱うため、単一の非パーティションテーブルより遅くなりやすい
期間を指定しての削除
DELETE FROM learning_history
WHERE created_at BETWEEN '2025-07-01 00:00:00' and '2025-07-01 23:59:59';
Query OK, 323470 rows affected (44.93 sec)
Query OK, 322767 rows affected (3 min 46.61 sec)
Query OK, 323164 rows affected (5 min 49.52 sec)
| 環境 | 実行時間 |
|---|---|
| learning_history | 44.93秒 |
| learning_history_indexed | 3分46.61秒 |
| learning_history_partitioned | 5分49.52秒 |
なぜ partitioned が遅いのか
- DELETE はパーティションプルーニングの恩恵を受けにくい: SELECT と異なり、DELETE は対象行を特定した後にインデックスの更新、トランザクションログの書き込み、行の物理削除など多くの処理が発生する
- インデックスメンテナンスのオーバーヘッド: partitioned テーブルは各パーティションごとにインデックスを持つため、削除時に複数のインデックスツリーを更新する必要がある
- トランザクションログの肥大化: パーティションをまたぐ操作では、パーティションごとのメタデータ管理やロック処理が追加で発生する
- インデックスなしが最速な理由: セカンダリインデックスが存在しないため、行の削除だけで済み、インデックスの更新が不要
LIMITを設定したDELETE
DELETE FROM learning_history
WHERE created_at BETWEEN '2025-08-01 00:00:00' and '2025-08-01 23:59:59' LIMIT 10000;
Query OK, 10000 rows affected (3 min 3.44 sec)
Query OK, 10000 rows affected (16.96 sec)
Query OK, 10000 rows affected (15.57 sec)
| 環境 | 実行時間 |
|---|---|
| learning_history | 3分3.44秒 |
| learning_history_indexed | 16.96秒 |
| learning_history_partitioned | 15.57秒 |
なぜ indexed / partitioned が速いと考えられるか?
LIMIT なしの DELETE とは逆の結果になっています。この理由として以下が考えられる:
-
インデックスによる効率的な行特定: LIMIT 10000 の場合、まず対象行を特定する必要がある。インデックスがあれば
idx_created_atを使って該当行を即座に特定できるが、インデックスなしはフルテーブルスキャンで10000行を見つける必要がある - 削除対象が少ないためインデックス更新のオーバーヘッドが軽減: 32万行を一括削除する場合と比較して、10000行なら インデックス更新のコストが大幅に減少
-
partitioned がわずかに速い理由: パーティションプルーニングにより
p202508のみを対象にするため、インデックスのサイズが小さく、行の特定と削除が効率的 - インデックスなしが最も遅い理由: 削除対象の10000行を見つけるために1.2億行をスキャンする必要がある
LIMIT なしとの比較:
| パターン | インデックスなし | indexed | partitioned |
|---|---|---|---|
| LIMIT なし(32万行削除) | 44.93秒 | 3分46秒 | 5分49秒 |
| LIMIT 10000 | 3分3秒 | 16.96秒 | 15.57秒 |
LIMIT なしではインデックス更新のオーバーヘッドが大きいが、LIMIT ありでは行の特定コストの方が大きくなるため、結果が逆転していると考えられる。
DROP PARTITION を使用した削除
ALTER TABLE learning_history_partitioned DROP PARTITION p202501;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
DROP PARTITION であれば0.05秒で削除完了。
実運用での対策:
- 大量削除が必要な場合は
DROP PARTITIONを使用する(数秒で完了) -
DELETEで削除する場合は、バッチサイズを小さく分割して実行する(例:LIMIT 10000を繰り返す) - パーティション単位での削除が可能な設計にしておくことが重要
日付条件なしの全ユーザー集計
SELECT user_id, COUNT(*)
FROM learning_history
GROUP BY user_id
ORDER BY user_id;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Sort: learning_history.user_id (actual time=184252..184273 rows=1e+6 loops=1)
-> Table scan on <temporary> (actual time=184045..184131 rows=1e+6 loops=1)
-> Aggregate using temporary table (actual time=184045..184045 rows=999999 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=3.12..15315 rows=120e+6 loops=1)
EXPLAIN: -> Group aggregate: count(0) (cost=22.1e+6 rows=1e+6) (actual time=19.2..60851 rows=1e+6 loops=1)
-> Covering index scan on learning_history_indexed using idx_user_created (cost=11.2e+6 rows=109e+6) (actual time=19.1..58249 rows=120e+6 loops=1)
EXPLAIN: -> Group aggregate: count(0) (cost=24e+6 rows=6307) (actual time=25.9..66262 rows=1e+6 loops=1)
-> Covering index scan on learning_history_partitioned using idx_user_created (cost=12.2e+6 rows=118e+6) (actual time=25.8..63397 rows=120e+6 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 3分41秒 |
| learning_history_indexed | 1分0.89秒 |
| learning_history_partitioned | 1分6.36秒 |
→ indexed の方が約 5 秒速い。
なぜ partitioned が遅いのか
- パーティションプルーニングが効かない: 日付条件がないため、全12パーティションをフルスキャン
- パーティション間のマージ処理: 各パーティションでの GROUP BY 結果をマージして最終結果を生成するオーバーヘッド
- インデックスの分散: indexed は単一の連続したインデックスを走査できるが、partitioned は12個の分割されたインデックスを順次走査
-
cost の差: indexed は
cost=22.1e+6に対し partitioned はcost=24e+6とやや高い
実運用での対策
- このような「全期間の全ユーザー集計」は日付条件を付けることが多いため、大きな問題にはならない
- どうしても必要な場合は、集計結果をキャッシュするか、バッチ処理で事前計算しておく
インデックスがパーティションより速いクエリ
このセクションのケースでは、多くの場合 partitioned の方がやや遅い 結果になっています。これは測定誤差ではなく、パーティション構造に起因するオーバーヘッドだと考えられる:
- 複数パーティションへのアクセス: 日付条件で十分に絞り込めない場合、複数のパーティションにまたがってインデックスを検索する必要がある
- マージ処理: 各パーティションから取得した結果をマージする処理が発生
- メタデータのオーバーヘッド: パーティションごとのメタ情報の管理コスト
ただし、差は数十ミリ秒程度であり、実用上は問題にならない。
1.2億程度ではインデックスが優秀なため、パーティションのオーバーヘッドが目立つ結果となった。
特定日の学習履歴取得
SELECT *
FROM learning_history
WHERE created_at = '2025-03-01';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: (learning_history.created_at = TIMESTAMP'2025-03-01 00:00:00') (cost=11.5e+6 rows=11.1e+6) (actual time=3203..17919 rows=3 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=2.1..15127 rows=120e+6 loops=1)
EXPLAIN: -> Index lookup on learning_history_indexed using idx_created_at (created_at=TIMESTAMP'2025-03-01 00:00:00') (cost=2.2 rows=2) (actual time=0.941..0.945 rows=2 loops=1)
EXPLAIN: -> Index lookup on learning_history_partitioned using idx_created_at (created_at=TIMESTAMP'2025-03-01 00:00:00') (cost=2.2 rows=2) (actual time=2.94..2.94 rows=2 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 17.92秒 |
| learning_history_indexed | 0.01秒 |
| learning_history_partitioned | 0.01秒 |
→ どちらも 0.01 秒。インデックスが効くので差がない。
なぜindexとpartitionedが同等なのか?
- 結果セットが小さい: 該当レコードがわずか2〜3件のため、インデックスで一発検索
- Index lookup で完結: B-Tree インデックスの探索コストが主で、パーティション分割のメリットが出にくい
-
パーティションプルーニングは効いている: partitioned は
p202503のみ検索するが、元々インデックスが十分高速なため差が出ない
ユーザー + 日付条件
SELECT * FROM learning_history
WHERE user_id = 1000 AND created_at >= '2025-03-25';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: ((learning_history.user_id = 1000) and (learning_history.created_at >= TIMESTAMP'2025-03-25 00:00:00')) (cost=11.5e+6 rows=3.7e+6) (actual time=3543..18582 rows=84 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=2.71..15844 rows=120e+6 loops=1)
EXPLAIN: -> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-03-25 00:00:00') (cost=6.62 rows=52.5) (actual time=0.208..0.227 rows=88 loops=1)
-> Covering index lookup on learning_history_indexed using idx_user_question_id_created (user_id=1000) (cost=6.62 rows=105) (actual time=0.19..0.213 rows=105 loops=1)
EXPLAIN: -> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-03-25 00:00:00') (cost=5.78 rows=44) (actual time=0.0591..0.0854 rows=79 loops=1)
-> Covering index lookup on learning_history_partitioned using idx_user_question_id_created (user_id=1000) (cost=5.78 rows=88) (actual time=0.0514..0.0762 rows=88 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 18.58秒 |
| learning_history_indexed | 0.03秒 |
| learning_history_partitioned | 0.09秒 |
→ どちらも 0.1 秒以下。複合インデックスが効くので十分高速。
なぜindexが速いのか?
- 単一インデックスで完結: indexed は1つの統一されたインデックスを検索するだけ
- パーティションのマージオーバーヘッド: partitioned は複数パーティション(この場合12個)に分散したインデックスを検索し、結果をマージする処理が発生
- 結果セットが小さい: 約100件程度のため、この差は0.06秒程度の微小な差として現れてると考えられる
ユーザー + IN句 + 日付条件
SELECT * FROM learning_history
WHERE user_id = 100
AND question_id IN (1, 2, 3, 4, 5)
AND created_at >= '2025-03-25';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: ((learning_history.user_id = 100) and (learning_history.question_id in (1,2,3,4,5)) and (learning_history.created_at >= TIMESTAMP'2025-03-25 00:00:00')) (cost=11.5e+6 rows=1.85e+6) (actual time=16414..16414 rows=0 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=4.48..13746 rows=120e+6 loops=1)
EXPLAIN: -> Filter: ((learning_history_indexed.user_id = 100) and (learning_history_indexed.question_id in (1,2,3,4,5)) and (learning_history_indexed.created_at >= TIMESTAMP'2025-03-25 00:00:00')) (cost=2.02 rows=5) (actual time=0.0265..0.0265 rows=0 loops=1)
-> Covering index range scan on learning_history_indexed using idx_user_question_id_created over (user_id = 100 AND question_id = 1 AND '2025-03-25 00:00:00' <= created_at) OR (user_id = 100 AND question_id = 2 AND '2025-03-25 00:00:00' <= created_at) OR (3 more) (cost=2.02 rows=5) (actual time=0.0257..0.0257 rows=0 loops=1)
EXPLAIN: -> Filter: ((learning_history_partitioned.user_id = 100) and (learning_history_partitioned.question_id in (1,2,3,4,5)) and (learning_history_partitioned.created_at >= TIMESTAMP'2025-03-25 00:00:00')) (cost=2.03 rows=5) (actual time=0.059..0.059 rows=0 loops=1)
-> Covering index range scan on learning_history_partitioned using idx_user_question_id_created over (user_id = 100 AND question_id = 1 AND '2025-03-25 00:00:00' <= created_at) OR (user_id = 100 AND question_id = 2 AND '2025-03-25 00:00:00' <= created_at) OR (3 more) (cost=2.03 rows=5) (actual time=0.0575..0.0575 rows=0 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 16.41秒 |
| learning_history_indexed | 0.04秒 |
| learning_history_partitioned | 0.18秒 |
→ どちらも 0.2 秒以下。複合インデックスの range scan が効果的。
なぜindexが速いのか?
- 単一インデックスへのアクセス: indexed は1つのインデックスで range scan が完結
- パーティションの接続オーバーヘッド: partitioned は複数パーティションに対して並列にアクセスし結果をマージするため、接続や調整のコストがかかる
- 結果が0件でも差が出る: 実際にデータがなくても、パーティション構造へのアクセス自体にオーバーヘッドがある
DISTINCT + 期間条件
SELECT DISTINCT question_id FROM learning_history
WHERE user_id = 100 AND created_at >= '2025-03-25';
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Table scan on <temporary> (cost=11.8e+6..11.9e+6 rows=3.7e+6) (actual time=15387..15387 rows=91 loops=1)
-> Temporary table with deduplication (cost=11.8e+6..11.8e+6 rows=3.7e+6) (actual time=15387..15387 rows=91 loops=1)
-> Filter: ((learning_history.user_id = 100) and (learning_history.created_at >= TIMESTAMP'2025-03-25 00:00:00')) (cost=11.5e+6 rows=3.7e+6) (actual time=2769..15387 rows=97 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=1..12699 rows=120e+6 loops=1)
EXPLAIN: -> Group (no aggregates) (cost=12.7 rows=56.5) (actual time=0.0852..0.159 rows=80 loops=1)
-> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-03-25 00:00:00') (cost=7.05 rows=56.5) (actual time=0.0766..0.143 rows=84 loops=1)
-> Covering index lookup on learning_history_indexed using idx_user_question_id_created (user_id=100) (cost=7.05 rows=113) (actual time=0.0711..0.0934 rows=113 loops=1)
EXPLAIN: -> Group (no aggregates) (cost=10.6 rows=46) (actual time=0.145..0.188 rows=81 loops=1)
-> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-03-25 00:00:00') (cost=6 rows=46) (actual time=0.14..0.175 rows=85 loops=1)
-> Covering index lookup on learning_history_partitioned using idx_user_question_id_created (user_id=100) (cost=6 rows=92) (actual time=0.13..0.164 rows=92 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 15.39秒 |
| learning_history_indexed | 0.02秒 |
| learning_history_partitioned | 0.07秒 |
→ どちらも 0.1 秒以下。
なぜindexが速いのか?
- 単一インデックスで即座に完結: indexed は1つのインデックスから user_id=100 のレコードを取得し、その場で DISTINCT 処理
- パーティション跨ぎのマージコスト: partitioned は複数パーティションからそれぞれ結果を取得し、最終的にマージして重複排除するため追加コストが発生
- 実測で3倍以上の差: 0.02秒 vs 0.07秒と、パーティション構造のオーバーヘッドが顕著に現れていると考えられる
日別集計(GROUP BY DATE)
SELECT DATE(created_at) as learning_date, COUNT(*)
FROM learning_history
WHERE user_id = 100 AND created_at >= '2025-03-01'
GROUP BY DATE(created_at);
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Table scan on <temporary> (actual time=15382..15382 rows=90 loops=1)
-> Aggregate using temporary table (actual time=15382..15382 rows=90 loops=1)
-> Filter: ((learning_history.user_id = 100) and (learning_history.created_at >= TIMESTAMP'2025-03-01 00:00:00')) (cost=11.5e+6 rows=3.7e+6) (actual time=2897..15381 rows=106 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=3.14..12686 rows=120e+6 loops=1)
EXPLAIN: -> Table scan on <temporary> (actual time=0.172..0.182 rows=83 loops=1)
-> Aggregate using temporary table (actual time=0.169..0.169 rows=83 loops=1)
-> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-03-01 00:00:00') (cost=7.05 rows=56.5) (actual time=0.0655..0.0951 rows=91 loops=1)
-> Covering index lookup on learning_history_indexed using idx_user_question_id_created (user_id=100) (cost=7.05 rows=113) (actual time=0.0597..0.0777 rows=113 loops=1)
EXPLAIN: -> Table scan on <temporary> (actual time=0.0721..0.0772 rows=79 loops=1)
-> Aggregate using temporary table (actual time=0.0707..0.0707 rows=79 loops=1)
-> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-03-01 00:00:00') (cost=6 rows=46) (actual time=0.0158..0.0456 rows=92 loops=1)
-> Covering index lookup on learning_history_partitioned using idx_user_question_id_created (user_id=100) (cost=6 rows=92) (actual time=0.0135..0.0385 rows=92 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 15.39秒 |
| learning_history_indexed | 0.02秒 |
| learning_history_partitioned | 0.07秒 |
→ どちらも 0.1 秒以下。
なぜindexが速いのか?
- 単一インデックスでの集計: indexed は1つのインデックスから取得したデータをそのまま集計
- パーティション間の集計マージ: partitioned は各パーティションで部分集計を行い、最終的にマージする必要がある
- 実測で3倍以上の差: 0.02秒 vs 0.07秒と、パーティション構造のオーバーヘッドが明確に現れていると考えられる
ORDER BY + LIMIT
SELECT * FROM learning_history
WHERE user_id = 100 AND created_at >= '2025-03-01'
ORDER BY created_at DESC
LIMIT 10;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Limit: 10 row(s) (cost=11.5e+6 rows=10) (actual time=18818..18818 rows=10 loops=1)
-> Sort: learning_history.created_at DESC, limit input to 10 row(s) per chunk (cost=11.5e+6 rows=111e+6) (actual time=18818..18818 rows=10 loops=1)
-> Filter: ((learning_history.user_id = 100) and (learning_history.created_at >= TIMESTAMP'2025-03-01 00:00:00')) (cost=11.5e+6 rows=111e+6) (actual time=3579..18818 rows=106 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=7.01..16128 rows=120e+6 loops=1)
EXPLAIN: -> Limit: 10 row(s) (cost=7.05 rows=10) (actual time=0.147..0.148 rows=10 loops=1)
-> Sort: learning_history_indexed.created_at DESC, limit input to 10 row(s) per chunk (cost=7.05 rows=113) (actual time=0.144..0.145 rows=10 loops=1)
-> Filter: (learning_history_indexed.created_at >= TIMESTAMP'2025-03-01 00:00:00') (cost=7.05 rows=113) (actual time=0.08..0.105 rows=91 loops=1)
-> Index lookup on learning_history_indexed using idx_user_question_id_created (user_id=100) (cost=7.05 rows=113) (actual time=0.074..0.089 rows=113 loops=1)
EXPLAIN: -> Limit: 10 row(s) (cost=6 rows=10) (actual time=0.092..0.0925 rows=10 loops=1)
-> Sort: learning_history_partitioned.created_at DESC, limit input to 10 row(s) per chunk (cost=6 rows=92) (actual time=0.0908..0.0908 rows=10 loops=1)
-> Filter: (learning_history_partitioned.created_at >= TIMESTAMP'2025-03-01 00:00:00') (cost=6 rows=92) (actual time=0.0376..0.0672 rows=92 loops=1)
-> Index lookup on learning_history_partitioned using idx_user_question_id_created (user_id=100) (cost=6 rows=92) (actual time=0.0335..0.0592 rows=92 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 18.83秒 |
| learning_history_indexed | 0.02秒 |
| learning_history_partitioned | 0.11秒 |
→ どちらも 0.1 秒以下。ORDER BY + LIMIT はインデックスで十分高速。
なぜindexが速いのか?
- 単一インデックスでのソート: indexed は1つのインデックスから取得した約100件をソートするだけ
- パーティション間のソートマージ: partitioned は各パーティションでソートした結果をマージソートする必要があり、追加コストが発生
- 実測で5倍以上の差: 0.02秒 vs 0.11秒と、ORDER BY を含むクエリではパーティションのマージオーバーヘッドが大きい
MAX/MIN(最新レコード取得)
SELECT MAX(created_at)
FROM learning_history
WHERE user_id = 100;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Aggregate: max(learning_history.created_at) (cost=12.6e+6 rows=1) (actual time=15495..15495 rows=1 loops=1)
-> Filter: (learning_history.user_id = 100) (cost=11.5e+6 rows=11.1e+6) (actual time=316..15495 rows=128 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=3.98..13071 rows=120e+6 loops=1)
EXPLAIN: -> Rows fetched before execution (cost=0..0 rows=1) (actual time=84e-6..126e-6 rows=1 loops=1)
EXPLAIN: -> Rows fetched before execution (cost=0..0 rows=1) (actual time=84e-6..125e-6 rows=1 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 15.50秒 |
| learning_history_indexed | 0.01秒 |
| learning_history_partitioned | 0.03秒 |
→ どちらも マイクロ秒単位 で返る。
なぜindexが速いのか?
- 単一インデックスの末尾参照: indexed は1つのインデックスの末尾を見るだけで MAX 値を取得する
- パーティション間のMAX比較: partitioned は各パーティションの末尾を取得し、その中から最大値を選ぶ追加処理が必要
- 実測で3倍の差: 0.01秒 vs 0.03秒と、シンプルな MAX でもパーティションのオーバーヘッドが現れている
日付カラムなしのユーザー検索
SELECT * FROM learning_history WHERE user_id = 100;
EXPLAIN ANALYZE 実行結果
EXPLAIN: -> Filter: (learning_history.user_id = 100) (cost=11.5e+6 rows=11.1e+6) (actual time=376..15880 rows=128 loops=1)
-> Table scan on learning_history (cost=11.5e+6 rows=111e+6) (actual time=4.41..13439 rows=120e+6 loops=1)
EXPLAIN: -> Covering index lookup on learning_history_indexed using idx_user_question_id_created (user_id=100) (cost=12.7 rows=113) (actual time=0.0642..0.08 rows=113 loops=1)
EXPLAIN: -> Covering index lookup on learning_history_partitioned using idx_user_question_id_created (user_id=100) (cost=11.7 rows=103) (actual time=0.019..0.0891 rows=103 loops=1)
| 環境 | 実行時間 |
|---|---|
| learning_history | 15.89秒 |
| learning_history_indexed | 0.01秒 |
| learning_history_partitioned | 0.03秒 |
→ パーティションプルーニングは効かないが、複合インデックスで十分高速。
なぜindexが速いのか?
- 単一インデックスで即座に完結: indexed は1つのインデックスで user_id=100 を検索するだけ
- 全パーティションへのアクセス: partitioned はパーティションプルーニングが効かず、全12パーティションに対してインデックス検索を実行
- パーティション接続のオーバーヘッド: 各パーティションへのアクセスと結果のマージ処理により、0.01秒 vs 0.03秒の差が発生していると考えられる
日付をキーとしてRANGEパーティションが必要なケース
上記検証結果から以下のケースに該当する場合、日付をキーとしたRANGEパーティションの導入を検討すべき:
1. 日付条件を含むクエリが多い
検証結果で 2〜11倍の高速化 が確認されたのは、すべて日付条件を含むクエリ:
| クエリパターン | 効果 |
|---|---|
| 直近7日間の履歴取得(BETWEEN) | 7.6倍速 |
| 期間以降の大量データ取得 | 11.6倍速 |
| 期間 + ユーザー別集計 | 2倍速 |
| 問題 + 日付条件 | 2.8倍速 |
| JOIN + 日付条件 | 9.4倍速 |
パーティションプルーニングが効くことで、大幅な高速化が実現できる。
2. 結果セットが大きいクエリがある
以下のような「大量データを扱うクエリ」で効果がある:
- 数万〜数百万件を返すクエリ
- 集計・分析系のバッチ処理
- 他テーブルとのJOIN
逆に、ユーザー単位で数十〜数百件を返すだけなら、インデックスで十分。
3. 古いデータの定期削除が必要
パーティションの大きなメリットの一つは DROP PARTITION による高速削除:
-- 通常のDELETE(ロック発生)
DELETE FROM learning_history WHERE created_at < '2025-01-01';
-- DROP PARTITION(ロックなし)
ALTER TABLE learning_history DROP PARTITION p202412;
1年以上前のデータを定期的に削除するような運用では、このメリットは非常に大きい。
4. データ量が1億行を超える見込み
今回は 1.2億行 で検証しましたが、数百万行程度ならインデックスで十分高速。
パーティション管理には以下のオーバーヘッドがあるため、データ量が少ないと割に合わない:
- DDL の複雑化
- パーティション追加の運用タスク
- PRIMARY KEY の制約
日付をキーとしたRANGEパーティションが必要ないケース
1. 結果セットが小さいクエリがメインの場合
今回の検証で、以下のケースでは indexed と partitioned がほぼ同等、むしろインデックスが速い場合も:
| クエリパターン | indexed | partitioned | 差 |
|---|---|---|---|
| 特定日の取得 | 0.01秒 | 0.01秒 | 同等 |
| ユーザー + 日付条件 | 0.03秒 | 0.09秒 | indexed が3倍速 |
| ユーザー + IN句 + 日付条件 | 0.04秒 | 0.18秒 | indexed が4.5倍速 |
| DISTINCT + 期間条件 | 0.02秒 | 0.07秒 | indexed が3.5倍速 |
| ORDER BY + LIMIT | 0.02秒 | 0.11秒 | indexed が5.5倍速 |
| MAX/MIN | 0.01秒 | 0.03秒 | indexed が3倍速 |
理由: 複合インデックスで十分絞り込める場合、パーティション間のマージ処理がオーバーヘッドになる。
ユーザー単位で数十〜数百件を返すクエリがメインなら、パーティションは不要。インデックス設計に力を入れた方がコスパがいい。
2. パーティションキー(日付)を WHERE で使わないクエリが多い場合
検証結果の「日付条件なしの全ユーザー集計」では:
- indexed: 1分0.89秒
- partitioned: 1分6.36秒
パーティションキーを条件に含めないと、全パーティションをスキャンするためオーバーヘッドが発生。
ただし、「日付カラムなしのユーザー検索」では indexed (0.01秒) と partitioned (0.03秒) でどちらも高速でした。これはインデックスが効くケースでは、全パーティションを検索しても各パーティション内でインデックスが効くため。
例えば以下のような 大量データを返すクエリ が多いシステムでは採用が難しい:
-- 日付条件なしの大量データ取得
SELECT user_id, COUNT(*) FROM learning_history GROUP BY user_id;
SELECT COUNT(*) FROM learning_history; <- アンチパターンのクエリなので実行することは稀だとは思うが...
3. データ量が少ない場合
数百万行程度なら:
- インデックスだけで十分高速(ミリ秒単位で返る)
- パーティション管理のオーバーヘッドが割に合わない
目安: 1億行を超えて、かつ日付ベースの大量データクエリがある場合に検討。
4. 古いデータの削除が不要な場合
全データを永続保存する要件であれば、DROP PARTITION のメリットが活かせない。
5. テーブル構造の制約が許容できない場合
MySQL のパーティションには制約があります:
-
PRIMARY KEY にパーティションキーを含める必要がある
-
PRIMARY KEY (id)→PRIMARY KEY (id, created_at)に変更が必要
-
- UNIQUE 制約にもパーティションキーが必要
- 外部キー制約が使えない
既存テーブルのリファクタリングや ORM との相性で採用が難しいことがある。
- Rails を使っていたシステムでは Active Record の主キー設定周りで修正が必要だった経験があり、Active Record は複合主キーをネイティブにサポートしていないので、
findやfirst/lastの挙動に影響が出た。
mikanのパーティション導入の判断フロー
データ量が 1億行 を超える見込み?
├─ No → インデックスで十分
└─ Yes
↓
日付条件を含む大量データクエリがある?
├─ No → インデックス設計を見直す
└─ Yes
↓
古いデータの定期削除が必要?
├─ No → クエリ性能だけで判断(効果が大きいなら導入検討)
└─ Yes
↓
PRIMARY KEY の変更が許容できる?
├─ No → パーティション以外の方法を検討
└─ Yes → パーティション導入を推奨 🎉
検証を終えて
結果としてインデックスが優秀すぎるため、一部検証ではパーティションのオーバーヘッドが目立つ検証結果となりました。
この結果は参考程度にとどめて、最終的にレコード総数が1億を超えることが想定されるならばパーティションの導入を検討することをおすすめします。
また、今回の調査の途中で複合インデックスがめっちゃ優秀なため、MySQLでのインデックスの限界行数(インデックスよりもパーティションが優位になる行)も調査してみたいと思いました。
ただそのためにはデータの用意に時間がかかりそうなので、コツコツデータを挿入してチャレンジしてみたいと思います。
今回はRANGEパーティションを検証しましたが将来的にばHASHパーティションの調査についてもチャレンジしたいです。
Discussion