
2026/01/20 23:23
**従来にとらわれない PostgreSQL の最適化**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
PostgreSQLは、インデックスサイズを削減しクエリ速度を向上させるためにいくつかの方法を提供します:
-
– クエリの述語がテーブルのチェック制約と一致できない場合、プランナーはそのパーティションまたはテーブルのスキャンをスキップできます。 デフォルトではconstraint_exclusion
に設定されていますが、BI/レポートワークロードでアドホッククエリに不可能な値(例:"partition"
のあるテーブルでCHECK (plan IN ('free','pro'))
)が含まれる場合はplan = 'Pro'
に変更すると便利です。"on" -
関数ベースのインデックス –
などの式をインデックス化することで、214 MB の B‑Tree インデックスを 66 MB に縮小し、クエリ時間を約 627 ms から約 145 ms に短縮しました。ただし、このインデックスは脆弱であり、その式に変更があればロジックがビューまたは仮想生成列で強制されない限り使用できなくなります。date_trunc('day', sold_at AT TIME ZONE 'UTC')::date -
仮想生成列(PostgreSQL 18) –
はデータを保存せず、同じ式を一貫してインデックス化することができます。 仮想列に対するインデックスはまだサポートされていないため、メリットは重複ストレージの回避と整合性の確保に限定されます。ALTER TABLE sale ADD sold_at_date DATE GENERATED ALWAYS AS (…) -
ハッシュベースの除外制約 – 大きなテキスト列(
)に対する標準的な一意制約は約 154 MB の B‑Tree インデックスを作成します。これをurls.url
ON CONFLICT (url) DO NOTHINGALTER TABLE urls ADD CONSTRAINT urls_url_unique_hash EXCLUDE USING HASH (url WITH =)`` に置き換えると、約 32 MB のハッシュインデックスが生成され、まだ一意性が保証されます。 制限点としては、外部キーで参照できないこと、
ON CONFLICT ON CONSTRAINTが
DO UPDATEを使用する必要があること、
ON CONFLICT ON CONSTRAINTはサポートされていないことがあります。 回避策としては
MERGE` 文の使用があります。の利用や、upsert 用に予定されている
影響:
- ユーザーはレポーティングシナリオで少ないバイト数を保存し、大きな文字列列で一意性を強制する際に高速クエリを実行できます。
- 開発者は関数ベースの式を安定させる(または仮想生成列を使用する)よう自律的に管理し、ハッシュ除外制約の制限を認識して適切な競合解決手法を採用する必要があります。
本文
データベース最適化 – PostgreSQL における非定番テクニック
1. チェック制約を活用してフルテーブルスキャンを排除
シナリオ
以下のように、
plan 列が free または pro のみを許容するチェック制約を持つテーブルがあります。
CREATE TABLE users ( id INT PRIMARY KEY, username TEXT NOT NULL, plan TEXT NOT NULL, CONSTRAINT plan_check CHECK (plan IN ('free', 'pro')) );
あるユーザーが誤って大文字の
'Pro' を検索すると、PostgreSQL は自動的に制約を利用して行を除外できません。
EXPLAIN ANALYZE SELECT * FROM users WHERE plan = 'Pro'; -- Seq Scan on users (cost=0..2185)
解決策 – constraint_exclusion
constraint_exclusionconstraint_exclusion を有効にすると、PostgreSQL は条件を満たせないテーブル(あるいはパーティション)をスキップできます。
SET constraint_exclusion TO 'on'; EXPLAIN ANALYZE SELECT * FROM users WHERE plan = 'Pro'; -- Result (cost=0..0) – no rows, no scan
いつ使うべきか
- BI ツールやレポート作成時にアドホックでクエリが実行される場合
- 入力ミス(大文字・小文字の混在など)が想定される環境
- 不要なフルテーブルスキャンを防ぎたいとき
2. 関数ベースインデックスで低カード性に最適化
シナリオ
数百万行からなる販売テーブルがあります。
CREATE TABLE sale ( id INT PRIMARY KEY, sold_at TIMESTAMPTZ NOT NULL, charged INT NOT NULL );
アナリストは次のような日次レポートを実行します。
SELECT date_trunc('day', sold_at AT TIME ZONE 'UTC'), SUM(charged) FROM sale WHERE '2025-01-01 UTC' <= sold_at AND sold_at < '2025-02-01 UTC' GROUP BY 1;
悪いアプローチ – sold_at
に対する B‑Tree
sold_atCREATE INDEX sale_sold_at_ix ON sale(sold_at); -- サイズ: 約214 MB(テーブルのほぼ半分)
性能は向上しますが、ストレージコストが大きくなります。
良いアプローチ – 日付に対する関数ベースインデックス
CREATE INDEX sale_sold_at_date_ix ON sale((date_trunc('day', sold_at AT TIME ZONE 'UTC'))::date); -- サイズ: 約66 MB
クエリを次のように書き換え、関数ベースインデックスを利用します。
SELECT date_trunc('day', sold_at AT TIME ZONE 'UTC'), SUM(charged) FROM sale WHERE date_trunc('day', sold_at AT TIME ZONE 'UTC')::date BETWEEN '2025-01-01' AND '2025-01-31' GROUP BY 1;
結果:
フルスキャン: 約627 ms → 関数ベースインデックス: 約145 ms(全インデックス使用時は約187 ms)
規律の問題
クエリは インデックスと同じ式を正確に使わなければ PostgreSQL はシーケンシャルスキャンに戻ります。
SELECT (sold_at AT TIME ZONE 'UTC')::date ...
解決策
| アプローチ | メリット | デメリット |
|---|---|---|
ビュー () | 正しい式が保証される | 直接テーブルにアクセスできてしまう |
| 仮想生成列 (PostgreSQL 18+) | 式をカラムとして保存、追加ストレージ不要 | 仮想列へのインデックスはまだサポートされていない |
ALTER TABLE sale ADD sold_at_date DATE GENERATED ALWAYS AS (date_trunc('day', sold_at AT TIME ZONE 'UTC'));
これにより、クエリは単に
sold_at_date を参照するだけでインデックスが利用されます。
3. ハッシュインデックスで一意性を保証
問題
URL が長大なテキスト (
text) の場合、B‑Tree インデックスは膨大になります。
CREATE TABLE urls ( id INT PRIMARY KEY, url TEXT NOT NULL, data JSON ); CREATE UNIQUE INDEX urls_url_unique_ix ON urls(url); -- サイズ: 約154 MB(テーブル 160 MB)
ハッシュインデックスで解決
-
排除制約を使ってユニークハッシュインデックスを作成
ALTER TABLE urls ADD CONSTRAINT urls_url_unique_hash EXCLUDE USING HASH (url WITH =); -
一意性の検証 – 重複挿入は失敗します。
-
クエリ性能 – B‑Tree と同等か、しばしば高速です。
-
サイズ比較
| インデックス | サイズ |
|---|---|
| 32 MB |
| 154 MB |
制限事項
- 外部キー参照に使用できない。
はサポートされていないので、ON CONFLICT … DO UPDATE
を使うか、制約名を指定してMERGE
とする必要があります。ON CONFLICT ON CONSTRAINT- 大きな文字列で一意性が必須で、FK 参照が不要なケースに有効です。
まとめ
| テクニック | 適用シナリオ | 主なメリット |
|---|---|---|
| constraint_exclusion | アドホック BI クエリや入力ミスが想定される環境 | 条件に合わないテーブルを完全にスキップ |
| 関数ベースインデックス/仮想生成列 | カード性が低い列(例: 日付) | インデックスサイズ削減、クエリ速度向上、規律の問題解消 |
| ハッシュインデックス (排除制約) | 長大な文字列で一意性を確保したいとき | 大幅に小さいインデックス、検索高速化 |
これらの非定番手法を取り入れることで、ストレージコストの削減、クエリ性能の向上、人為的ミスへの耐性強化が実現できます。