なぜ今さら「CHECK制約」の話をするのか?
現場でPostgreSQLをいじっていると、ついついインデックスの貼り方とか、複雑なクエリのチューニングにばかり目が行きがちだよね。でも、実は「最強のパフォーマンスチューニング」は、アプリケーションコードを書く前に、スキーマ設計の段階で終わっていることが多いんだ。
今日は、みんなが意外と見落としがちな「CHECK制約」について深掘りしていくよ。単なる「データの番人」だと思っているなら、それは非常にもったいない。こいつは、PostgreSQLのクエリプランナを味方につけるための、最高の武器なんだから。
—
CHECK制約は「最強のドキュメント」だ
まず基本のおさらいだけど、CHECK制約は「この列にはこの値しか入れさせないよ」というルールをデータベースレベルで強制するものだよね。
ALTER TABLE orders
ADD CONSTRAINT check_positive_price
CHECK (price > 0);
初心者のうちは「これ、アプリ側のバリデーションでいいじゃん」って思うかもしれない。でも、思い出してほしい。そのデータベース、Webアプリからだけじゃなくて、バッチ処理や他のマイクロサービス、あるいは直接コンソールから誰かが触るかもしれないんだ。「データが汚れないこと」をDBが保証してくれる安心感は、何物にも代えがたいよ。
—
クエリプランナが「賢くなる」瞬間
ここからが本題。僕がCHECK制約を愛してやまない最大の理由は、クエリプランナが「条件推論」をしてくれるからなんだ。
例えば、こんなテーブルがあったとするよ。
CREATE TABLE sensor_data (
id serial PRIMARY KEY,
reading_type text,
value numeric,
recorded_at timestamptz
);
ここで、データが数億行に膨れ上がったとき、`reading_type = ‘temperature’` のデータだけを検索するクエリを叩くと、PostgreSQLは律儀に全件をスキャン(あるいはインデックス全体を走査)しようとする。
でも、もしここで「テーブル継承」や「パーティショニング」を使っていて、CHECK制約が付いていたらどうなるか。
— 温度データ用のパーティションテーブル
CREATE TABLE sensor_data_temp (
CHECK (reading_type = ‘temperature’)
) INHERITS (sensor_data);
クエリプランナは、「お、このテーブルには温度データしか入っていないことがCHECK制約で保証されてるな。じゃあ他のテーブルは無視していいや!」と判断する。これを「Constraint Exclusion(制約による除外)」と呼ぶんだけど、これを知っているだけでクエリの実行速度が桁違いに変わるんだ。
—
実践:範囲の絞り込みで爆速にする
CHECK制約は、単なる等価比較だけじゃない。範囲指定も非常に強力だ。
ALTER TABLE user_actions
ADD CONSTRAINT check_action_date
CHECK (created_at >= ‘2023-01-01’ AND created_at < '2024-01-01');
こうしておけば、もし将来的に「2023年のデータだけ抽出して」というクエリが走ったとき、プランナは迷いなくその期間のパーティションだけをピンポイントで叩きに行く。アプリケーション側で必死に条件分岐を書く必要なんてなくなるんだ。
---
注意点:制約は「重すぎない」範囲で
ここまで持ち上げたけど、一つだけ注意点。CHECK制約の中に、別のテーブルを参照するような重い関数や複雑なサブクエリを入れないでほしい。
CHECK制約は、INSERTやUPDATEのたびに評価される。ここにコストの高い処理を詰め込むと、書き込み性能がガタ落ちする。「シンプルに、列の値の範囲や形式を規定する」という本来の役割に徹させるのが、プロの設計というものだよ。
—
結論:DBに「推論」させよう
優秀なデータベースエンジニアは、DBに「命令」するだけじゃなくて、DBが「推論」しやすい環境を整えるのが上手い人だと思う。
CHECK制約を適切に設定することは、データベースという巨大なエンジンに対して、「ここはこういうデータしか来ないから、安心して最短距離で走っていいぞ」とヒントを与える作業なんだ。
次にテーブルを作るときは、ぜひ「この列の正当性をDBに教え込むにはどうすればいいか?」を少しだけ考えてみてほしい。それだけで、君の書くスキーマは一段階上のレベルに到達するはずだよ。
それじゃあ、また現場で会おう。いい設計を!
コメント