PostgreSQL JSONBクエリ最適化 - GINインデックスで高速検索

問題

ユーザー設定をJSONBカラムに保存しましたが、特定のキーで検索するとフルスキャンが走って遅くなりました。

-- metadataカラムがJSONBのテーブル
SELECT * FROM users
WHERE metadata->>'role' = 'admin';
-- Seq Scan... 遅い

解決方法

GINインデックスを作成すると、JSONB内部のキー・値検索がインデックスを利用できます。

-- GINインデックスを作成
CREATE INDEX idx_users_metadata ON users USING GIN (metadata);

-- @>演算子で検索(GINインデックスを使用)
SELECT * FROM users
WHERE metadata @> '{"role": "admin"}';

特定のキーだけを頻繁に検索する場合は、Expressionインデックスがより効率的です。

-- 特定パスにB-treeインデックス
CREATE INDEX idx_users_role ON users ((metadata->>'role'));

-- このクエリがインデックスを利用するようになります
SELECT * FROM users
WHERE metadata->>'role' = 'admin';

ネストされたJSONもパス演算子でアクセスできます。

-- ネストされたキーへのアクセス
SELECT * FROM users
WHERE metadata #>> '{address,city}' = 'Seoul';

-- jsonpathも使用可能(PostgreSQL 12以降)
SELECT * FROM users
WHERE metadata @? '$.tags[*] ? (@ == "vip")';

ポイント

  • @>包含演算子を使うことでGINインデックスが利用されます。->>演算子にはGINではなくExpressionインデックスが必要です
  • GINインデックスは書き込みが遅くなるトレードオフがあります。読み取り中心のテーブルに適しています
  • JSONBはバイナリ形式で保存されるため、JSONより検索が高速です。カラム型はJSONではなくJSONBを使いましょう