【ITニュース解説】PostgreSQLで同じ結果が得られるクエリの実行計画を確認してみる

作成日: 更新日:

ITニュース概要

PostgreSQLで同じ結果を返す「UNION」と「CASE式」の二つの書き方について、それぞれのクエリがデータベースでどう処理されるか(実行計画)を比較し、どちらが速く動くか(パフォーマンス)を検証した。

ITニュース解説

データベースの効率性は、システム全体の性能に直結する非常に重要な要素だ。同じ結果が得られるSQLクエリであっても、その書き方によってデータベースが裏側でどのように処理するか、そして処理にかかる時間は大きく異なることがある。この記事では、PostgreSQLという代表的なデータベースシステムを例に、その違いを「実行計画」という仕組みを通して確認する方法を解説する。特に、複数のデータの結合によく使われる「UNION」と、条件によってデータの値を変更する「CASE式」という二つのSQLの書き方を比較し、どちらがより効率的に動作するのかを見ていく。 まず、データベースにおける「実行計画」とは何かを理解することが重要だ。データベースがSQLクエリを受け取ったとき、すぐにデータを取得し始めるわけではない。まず、そのクエリを最も効率的に実行するための「最適な手順書」のようなものを内部的に作成する。これが実行計画だ。例えるなら、目的地までの道のりを探すカーナビのようなものだ。カーナビが道の混雑状況や距離、時間などを考慮して最適なルートを提案するように、データベースもテーブルの構造、データの量、インデックスの有無といった情報を元に、どのテーブルからデータを読み込み、どのように結合し、どのように並び替えるか、といった具体的な手順を決定する。この計画を確認することで、なぜあるクエリは速く、別のクエリは遅いのか、その原因を深く分析できるようになる。`EXPLAIN`コマンドや、より詳細な情報と実際の実行時間を確認できる`EXPLAIN ANALYZE`コマンドを使うことで、この実行計画を見ることができる。 次に、データベースにおける「パフォーマンスが良い」とはどういうことかについて触れる。単に処理が速いだけでなく、コンピューターのリソースを効率的に使うことを指す。具体的には、クエリの実行にかかる時間(応答速度)、CPUの使用率、メモリの使用量、そしてディスクへの読み書き(I/O)の回数などが挙げられる。システムエンジニアは、これらの要素を総合的に判断し、ユーザーが快適にシステムを利用できるように、データベースのクエリを最適化する責任がある。実行計画は、パフォーマンスのボトルネック、つまり処理が遅くなる原因となっている箇所を見つけ出すための重要な手がかりとなる。 記事では、同じ結果を返すために「UNION ALL」と「CASE式」という二つの異なるアプローチを比較している。 「UNION ALL」は、複数の`SELECT`文の実行結果を縦方向に結合するSQLの演算子だ。例えば、「タイプAのデータだけを抽出する`SELECT`文」と、「タイプBのデータだけを抽出する`SELECT`文」を用意し、それらの結果を一つにまとめて表示したい場合に利用する。`UNION ALL`は、重複する行があってもそのまま全て結合するため、データベースはそれぞれの`SELECT`文を個別に実行し、その後にそれらの結果を一つにまとめるという複数の工程を経ることになる。 一方、「CASE式」は、プログラミング言語における`if-else`文のように、指定した条件に基づいて値を変更したり、異なる結果を返したりするSQLの構文である。例えば、あるカラムの値が1なら「A」と表示し、2なら「B」と表示したい場合に使う。この場合、データベースは通常、テーブルを一度だけ読み込み、その読み込みの過程で各レコードの条件を評価し、適切な値を生成する。 記事の具体的な実験内容を見ていこう。`id`、`group_id`、`name`、`type`というカラムを持つシンプルなテスト用テーブルを用意し、`type`カラムの値に応じて特定の文字列(例:「テストA」、「テストB」)を返すように加工するという目的のクエリを作成する。 UNION ALLを使ったクエリでは、`type`が1のレコードを取得する`SELECT`文と、`type`が2のレコードを取得する`SELECT`文の二つを書き、それらを`UNION ALL`で結合する。これにより、データベースは二つの独立したデータ抽出処理を実行した後、それらの結果を一つに結合する。 対照的に、CASE式を使ったクエリでは、一つの`SELECT`文の中で`CASE WHEN type = 1 THEN 'テストA' WHEN type = 2 THEN 'テストB' ELSE NULL END`のように記述し、`type`の値に応じて表示する文字列を切り替える。この場合、データベースはテーブル全体を一度だけ読み込み(これをシーケンシャルスキャンと呼ぶ)、その読み込みの最中に各レコードの`type`カラムの値を評価し、表示する文字列を決定する。 これらのクエリを`EXPLAIN ANALYZE`コマンドで実行し、実行計画と実際の実行時間を確認すると、顕著な違いが見られる。UNION ALLの実行計画では、「Sort(並び替え)」や「Append(結合)」といった複数の操作が記録され、その中で二つの「Seq Scan(シーケンシャルスキャン)」が実行されていることがわかる。これは、二つの異なる`SELECT`文がそれぞれテーブルをスキャンし、その結果が結合されるという処理フローを示している。一方、CASE式の実行計画では、単一の「Seq Scan」で処理が完結していることが示される。これは、テーブルを一度読み込むだけで、その中で条件分岐の処理が行われていることを意味する。結果として、CASE式を使ったクエリの方が、UNION ALLを使ったクエリよりも実際の実行時間が短く、パフォーマンスが良いという結論が示されている。 この比較から得られる最も重要な教訓は、データベースに対するSQLクエリは、たとえ同じ結果が得られるとしても、その書き方によってデータベースの内部的な処理方法、つまり実行計画が大きく異なり、それが最終的なパフォーマンスに大きな差を生むという点だ。システムエンジニアとしてデータベースを扱う際には、単に目的のデータを取得できるだけでなく、そのデータ取得が最も効率的であるかを常に意識する必要がある。そのためには、`EXPLAIN`や`EXPLAIN ANALYZE`のようなコマンドを活用して実行計画を確認し、クエリのボトルネックを特定し、より効率的な書き方を追求するスキルが不可欠だ。この知識は、ユーザーに快適なシステム体験を提供し、システムの安定稼働を支える上で非常に重要な基礎となる。

【ITニュース解説】PostgreSQLで同じ結果が得られるクエリの実行計画を確認してみる