1. はじめに
新人のリュウさんが初出勤でデータベースを開いたとき、目が点になりました:
orders_202301
orders_202302
orders_202303
...
orders_202409
注文を1件調べるのに数十個のテーブルを結合する必要があり、集計作業はさらに大変でした。
リュウさんは先輩に尋ねました:「どうして1つのテーブルにまとめないんですか?」
先輩はため息をついて言いました:「昔は MySQL が今ほど性能が良くなかったから、分割しないとダメだったんだよ…」
しかし今では、この古いやり方はもはや必要ありません。
2. 歴史的背景:なぜ昔は分割テーブルが必要だったのか
MySQL 5.x の時代、テーブル分割は常識でした。その理由は以下の通りです:
- 性能の限界:1テーブルが数千万行を超えると、書き込みロックの競合が増え、検索が遅くなる。
- データ管理の容易さ:月ごとにテーブルを分けることで、過去データの削除が簡単。
-
DDL の安全性不足:大規模テーブルの
ALTER TABLE
はコピーを伴い、リスクが高い。 - バックアップ/リカバリの遅さ:大規模テーブルのエクスポート/インポートに時間がかかる。
- ツールやハードの制約:分割機能は未成熟で、SSDやメモリも高価。
- 業界の慣習:チーム内で「1テーブル千万行は分割必須」という経験則が定着。
要するに、当時の分割テーブルはやむを得ない妥協策でした。
3. MySQL 8 の変化
MySQL 8 では状況が大きく変わりました:
- 原子化された DDL:大規模テーブルでもオンラインで安全に変更可能。
- パーティションテーブル:論理的には1テーブル、物理的には分割されており、月跨ぎの検索も透過的。
- InnoDB の性能向上:数千万行でも適切なインデックスを使えばミリ秒単位での検索が可能。
-
バックアップツールの成熟:
XtraBackup
など大規模テーブルに対応。 - ハードコストの低下:SSD + 大容量メモリで IO とキャッシュのボトルネックが緩和。
4. 実務経験
私のプロジェクト経験:
-
注文テーブル:2,000万行、主キーまたはユーザー+日時で検索、平均応答時間は
1.5〜2ms
程度。 -
ログテーブル:5,000万行、インデックス付き検索で平均
2~4ms
。
成功のポイント:
- 主キー + 複合インデックス + カバリングインデックス
- Buffer Pool を十分に確保し、ホットデータをメモリに保持
- インデックス列に関数を使わない
- SSD による IO 改善
⚠️ 注意:キャッシュが十分でない場合、インデックスを使用しても性能は安定しません。
5. パーティションテーブルの例
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
order_amount DECIMAL(10,2),
order_date DATE NOT NULL,
PRIMARY KEY (order_id, order_date)
)
PARTITION BY RANGE (YEAR(order_date)*100 + MONTH(order_date)) (
PARTITION p202301 VALUES LESS THAN (202302),
PARTITION p202302 VALUES LESS THAN (202303),
PARTITION p202303 VALUES LESS THAN (202304),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
検索例:
SELECT * FROM orders
WHERE order_date BETWEEN '2023-02-01' AND '2023-02-28'
AND user_id = 10001;
- オプティマイザは
p202302
のパーティションのみをスキャンし、全表検索は行いません。 - 新しいパーティションの追加も簡単です:
ALTER TABLE orders
ADD PARTITION (PARTITION p202310 VALUES LESS THAN (202311));
👉 注意点:パーティションキーはすべてのユニークインデックスに含める必要があります。
6. 比較:従来の分割テーブル vs MySQL 8
方法 | 検索の複雑さ | メンテナンスコスト | 性能 |
---|---|---|---|
月ごとの分割テーブル | 高(複数テーブルを結合) | 高(スクリプト・ORMサポートが弱い) | 効果は限定的 |
MySQL 8 パーティションテーブル | 低(透過的) | 低(簡単に管理可能) | 高(ミリ秒単位の検索) |
7. 結論
- 数千万行 = 必ず分割する必要なし
- MySQL 8 のベストプラクティス:パーティションテーブル + インデックス最適化 + キャッシュ
- 分割テーブル/シャーディングは、極端な大規模または高負荷環境でのみ必要
- 言い換えれば、リュウさんが今入社したとしても、目にするのは整理された1つの
orders
テーブルであり、無数のテーブルに悩まされることはありません