【PostgreSQL】処理速度を上げるための実践テクニック4選

スポンサーリンク

PostgreSQLは、オープンソースながら高機能なデータベースとして多くの現場で利用されています。しかし、データ量の増加や複雑なクエリ処理が進むと、「クエリの実行速度が遅い」「レスポンスが悪くなった」といったパフォーマンスの問題に直面することも少なくありません。

本記事では、「PostgreSQLの処理速度を上げる」ために実践できるテクニックを、できるだけわかりやすく解説していきます。インデックスの使い方からクエリの最適化、設定ファイルのチューニングまで、幅広い視点からパフォーマンス改善のポイントを紹介します。

「最近、データベースが重いな・・・」と感じている方は、ぜひこの記事を参考に、改善への第一歩を踏み出してみてください。

インデックスの適切な活用

データベースの処理速度を上げるうえで、まず注目したいのがインデックスの活用です。インデックスは、書籍の巻末にある索引のようなもので、検索処理を効率化してくれる仕組みです。特にデータ量が増えてくると、その効果は絶大です。

基本のインデックス

PostgreSQLでは、テーブルの特定の列に対してインデックスを作成できます。例えば、以下のようなSQLでインデックスを張ることができます。

CREATE INDEX idx_users_email ON users(email);

このようにインデックスを張ることで、email列を使った検索の処理速度が大きく向上します。

インデックスが効かないケースに注意

インデックスを作成していても、以下のようなケースではインデックスが使われない(スキャンされない)ことがあります。

  • 関数を使った検索(例:LOWER(email) = 'test@example.com'
  • ワイルドカードの前方一致(例:LIKE '%example.com'
  • キャストや計算式を含む検索
  • 統計的に選択度が低い(=ヒット数が多すぎる)列

こうした場合は、関数インデックス(式インデックス)の活用や、クエリの書き方の工夫が必要です。

-- 関数インデックスの例
CREATE INDEX idx_users_lower_email ON users(LOWER(email));

効果を確認するには?

インデックスが実際に効いているかどうかを確認するには、EXPLAINまたはEXPLAIN ANALYZEを使います。

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

「Seq Scan(全件走査)」ではなく「Index Scan」と表示されていれば、インデックスが利用されています。

クエリの最適化

いくらハードウェアや設定を整えても、クエリそのものが非効率だと処理速度はなかなか改善しません。実際、パフォーマンス問題の多くは「クエリの書き方」に起因しているケースが多いです。

この章では、よくあるクエリの落とし穴や、パフォーマンスを向上させるためのポイントを紹介します。

EXPLAIN / EXPLAIN ANALYZE でボトルネックを把握

まず最初にやるべきは、現状のクエリがどのように実行されているかを知ることです。

EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 123;

このように書くと、PostgreSQLがどのようにテーブルをスキャンし、どれくらい時間がかかっているかがわかります。

注目すべきポイントは以下の通りです。

  • Seq Scan(全件走査)なのか、Index Scanが使われているか
  • 実行時間(Total runtime)
  • 行数の見積もり精度(rows)

これを使って、「どこにムダがあるか」を探すのがクエリ改善の第一歩です。

サブクエリ vs JOIN の使い分け

複雑なクエリになってくると、「サブクエリ」と「JOIN」の使い分けが重要になります。

-- サブクエリ例(非効率になりがち)
SELECT * FROM orders 
WHERE customer_id IN (SELECT id FROM customers WHERE region = 'Tokyo');

-- JOINを使った例(効率的なケースが多い)
SELECT o.* FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.region = 'Tokyo';

JOINの方が、インデックスを活用しやすく、実行計画も安定しやすい傾向があります。もちろんケースバイケースですが、パフォーマンスが悪いサブクエリはJOINへの書き換えを検討してみましょう。

CTE(WITH句)の使いすぎに注意

PostgreSQLではCTE(Common Table Expression)を使ってクエリを見やすく整理できますが、実はパフォーマンス的には注意が必要です。

CTEはPostgreSQL上では一時的なサブクエリとして毎回フル実行されるため、JOINやサブクエリよりも遅くなることがあります。

-- パフォーマンスに影響を与える可能性がある例
WITH recent_orders AS (
  SELECT * FROM orders WHERE created_at > now() - interval '7 days'
)
SELECT * FROM recent_orders WHERE amount > 10000;

最適化されないCTEは実行計画が分断されるため、大量データを扱う場合は注意が必要です。

LIMIT / OFFSET の落とし穴

大量データをページングで扱うときによく使うLIMITOFFSETですが、OFFSETが大きいとレスポンスが遅くなることがあります。

SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 10000;

このクエリでは、PostgreSQLは1万件をスキップしてからデータを返す必要があるため、意外と重い処理になります。

対策としては、

  • カーソル方式やIDの範囲指定を検討する
  • 前回取得したIDを元に次ページを取得するスタイルにする

不要なデータアクセスの削減

PostgreSQLの処理速度を上げるうえで、もう一つ重要なのが「いかに無駄なデータを読まないか」という視点です。どんなにインデックスやクエリが工夫されていても、そもそも不要なデータまで取得してしまうと意味がありません

この章では、データアクセスを最小限に抑えるためのポイントを紹介します。

必要なカラムだけをSELECTする

よくあるパターンが、何も考えずに「SELECT *」を使ってしまうこと。

-- 悪い例:全カラムを取得してしまう
SELECT * FROM users WHERE status = 'active';

このような書き方は、不要なカラムまで読み込んでしまい、I/Oコストが無駄に高くなる原因になります。特にテーブルにBLOBやTEXTのような重たいカラムがある場合は注意が必要です。

-- 良い例:必要なカラムだけを明示する
SELECT id, name, email FROM users WHERE status = 'active';

これだけでも、レスポンスが目に見えて速くなるケースもあります。

適切なフィルター(WHERE句)の活用

クエリで条件を絞ることは基本中の基本ですが、WHERE句の書き方次第でインデックスの有無にも影響します。

以下のような書き方は、インデックスが効かない典型例です。

-- NG例:関数を使ってしまっている
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';

これを回避するためには、関数インデックスを使うか、クエリ側での関数利用を避けるようにしましょう。

テーブルパーティショニングの活用

テーブルのデータが数千万件、億単位に達すると、いくらインデックスがあっても処理に限界があります。
そんな時に有効なのが「テーブルパーティショニング(分割)」です。

パーティショニングを使えば、PostgreSQLは必要なパーティション(物理的な小さなテーブル)だけにアクセスするようになり、スキャンコストを大きく削減できます。

-- 例:日付ごとのパーティションに分割
CREATE TABLE orders (
  id SERIAL,
  customer_id INT,
  created_at DATE,
  amount NUMERIC
) PARTITION BY RANGE (created_at);

たとえば、「2024年4月以降のデータだけを検索したい」というようなケースで、不要な過去データをスキャンせずに済むようになります。

ビューやマテリアライズドビューの使い分け

よく使う集計結果や結合結果は、ビュー(VIEWマテリアライズドビュー(MATERIALIZED VIEW)として定義することで、毎回のクエリ処理を軽くできます。

  • ビュー:動的にデータを取得(都度実行される)
  • マテリアライズドビュー:一度実行して結果を保存、更新は明示的に行う

特に重いJOINGROUP BYを含むクエリは、マテリアライズドビュー化すると効果的です。

autovacuum の設定確認

PostgreSQLには、自動的にテーブルのメンテナンスを行ってくれる便利な機能があります。それがautovacuum(オートバキューム)。
ただし、このautovacuumの挙動が適切でないと、パフォーマンスがジワジワと悪化していく原因になります。

autovacuumとは?

PostgreSQLはMVCC(マルチバージョン同時実行制御)という仕組みによって、更新や削除があってもすぐには行を物理的に消さずに“過去バージョン”として残します。

このため、以下のような問題が発生します:

  • 不要なデータが蓄積し、テーブルサイズが膨張する
  • インデックスの精度が落ちる
  • クエリのスキャン効率が悪化する

これらを自動的にクリーンアップしてくれるのがautovacuumです。

実行状況を確認する

まずは現在のautovacuumの実行状況を確認してみましょう。

SELECT relname, last_autovacuum, last_analyze
FROM pg_stat_user_tables
ORDER BY last_autovacuum DESC;

このクエリで、各テーブルが最後にいつautovacuumされたかが分かります。
もし、更新が頻繁なテーブルで「last_autovacuumがNULL」の場合、それは要注意!

パラメータ設定を見直そう

autovacuumの挙動はPostgreSQLの設定ファイル(通常はpostgresql.conf)でコントロールできます。

主要なパラメータ

パラメータ名内容
autovacuum_vacuum_thresholdVACUUM実行の行数しきい値(デフォルト50)
autovacuum_vacuum_scale_factorVACUUM実行の割合しきい値(デフォルト0.2 = 20%)
autovacuum_naptime実行間隔(デフォルト1分)
autovacuum_max_workers同時に動けるワーカー数(デフォルト3)

特定のテーブルだけautovacuumを強化したいときは、ALTER TABLEで個別設定もできます。

ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.05);

この例では、「ordersテーブルは5%更新されたらvacuumしてね」という設定になります。

autovacuum の負荷とチューニング

autovacuumは便利ですが、裏で勝手に動いているため、タイミングが悪いとパフォーマンスに悪影響を与えることもあります。

そんな時は、

  • 実行頻度をコントロールする
  • 影響の大きいテーブルは手動でVACUUM/ANALYZEする
  • autovacuum_vacuum_cost_limit などで負荷の上限を調整する

といった対応で、バランスの良いチューニングが可能です。

まとめ

本記事では、PostgreSQLのパフォーマンスを向上させるためにいくつかの方法を紹介しました。

PostgreSQLのパフォーマンスを向上させるには、「設定をいじれば速くなる」なんて単純な話ではなく、クエリ・データ構造・システム設定の三位一体の見直しが必要です。

処理速度が上がると、アプリも、ユーザー体験も、運用のストレスもすべてが変わります。
「なんとなく使ってる」から「ちゃんと理解して速く使う」へ
これが、PostgreSQLと本気で向き合う第一歩です。

日々の運用の中で「速さ」と「安定性」の両立を目指すためにも、今回の内容が皆さまの参考になれば幸いです。

PostgreSQL
スポンサーリンク
なんくるをフォローする

コメント

タイトルとURLをコピーしました