PostgreSQLのEXPLAINでクエリーを知ろう

こんにちは、さるまりんです🐒

「このクエリ、遅い気がするけど、なんで?」

そんなときに役立つのが、PostgreSQLのEXPLAINコマンド!クエリがどう実行されるか、内部の計画を教えてくれる便利なツール。
実はこれまで使ったことがありませんでした。
今回はEXPLAINEXPLAIN ANALYZEを使って、クエリの動作を見て、改善していくことにチャレンジしてみたいと思います。

環境構築:DockerでPostgreSQLをすぐ試そう!

PostgreSQLを試せる環境をDockerでパパッと立ち上げます。

docker-compose.ymlはこれです。

version: '3'
services:
  db:
    image: postgres:16
    container_name: postgres-explain
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

起動します

docker-compose up -d

接続します

docker exec -it postgres-explain psql -U postgres -d testdb

 まずはサンプルテーブルで確認

PostgreSQLにusersというサンプルテーブルを作って、適当にテストデータを1万件入れてみます。

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT,
  age INT
);

-- 適当にデータを入れる(例:1万件)
INSERT INTO users (name, age)
SELECT 'user_' || i, (random() * 100)::int
FROM generate_series(1, 10000) AS s(i);

EXPLAINで実行計画を見る

次に、シンプルなクエリにEXPLAINをつけてみます。

EXPLAIN SELECT * FROM users WHERE age > 30;

実行結果:

QUERY PLAN                       
-------------------------------------------------------
 Seq Scan on users  (cost=0.00..189.00 rows=6972 width=17)
   Filter: (age > 30)

Seq Scanとなっていますね。これは「全件走査」の意味。
つまり、インデックスを使わずにすべての行をチェックしたということです。
cost は実行コストの推定値で、数値そのものよりも他のクエリとの相対的な比較に使います。
rows は、条件にマッチすると予測されている行数です。

インデックスが無いので、シーケンシャルスキャンになってますね。

インデックスを作って再確認

CREATE INDEX idx_age ON users(age);

インデックスを作ったら、その効果を反映させるために ANALYZE を実行します:

ANALYZE users;

そして再度EXPLAINを実行:

EXPLAIN SELECT * FROM users WHERE age = 99;

結果:

Bitmap Heap Scan on users  (cost=5.15..72.27 rows=112 width=17)
   Recheck Cond: (age = 99)
   ->  Bitmap Index Scan on idx_age  (cost=0.00..5.12 rows=112 width=0)
         Index Cond: (age = 99)

Index Scanの文字がありますね。
無事、インデックスが使われています!

EXPLAIN ANALYZEで実行時間もチェック!

実行計画に加えて、実際の実行時間も知りたいときはEXPLAIN ANALYZEを使います。

EXPLAIN ANALYZE SELECT * FROM users WHERE age > 30;

結果:

QUERY PLAN                                                
--------------------------------------------------------------------------------------------------------
 Seq Scan on users  (cost=0.00..189.00 rows=6972 width=17) (actual time=0.009..0.743 rows=6996 loops=1)
   Filter: (age > 30)
   Rows Removed by Filter: 3004
 Planning Time: 0.052 ms
 Execution Time: 0.913 ms

actual timeは実際にかかった時間、rowsは実際に返された行数です。
Rows Removed by Filterは条件に合わずスキップされた行、
Planning Time / Execution Time は全体の所要時間を示しています。

実行プランに「実際の数字」が付くことで、推定と実際のズレもわかってきますね。

まとめ

遅いクエリがあったら、まずは EXPLAIN
実行計画を見て、インデックスが効いているか確認。
インデックスを作ったら ANALYZE で統計情報を反映してから、もう一度 EXPLAIN
時間も知りたいときは EXPLAIN ANALYZE です。

これまで使っていませんでしたが、「おや?」と思ったときは積極的に使ってみたいと思います。
便利に、上手に使って、快適に。

読んでくださってありがとうございました。

それではまた!