クエリのパフォーマンスを決める実行計画を見てみよう
さて,こうして実行計画が作られると,DBMSはそれをもとにしてデータアクセスを行います。しかし,データ量の多いテーブルへアクセスしたり,複雑なクエリを実行する場合,クエリのレスポンスが遅いことがよくあります。この理由には,先述のように統計情報が不正確であるという原因もありますが,それ以外にも,そもそも現状で最適なアクセスパスが選択されているのに遅い,ということもあります。また,統計情報は最新でも,クエリが複雑過ぎてオプティマイザが最適なアクセスパスを作成できないこともあります。
こうした理由からクエリの遅延が発生したとき,最初に調べるべき対象が実行計画です。DBMSはいずれも,実行計画を調べる手段を提供しています。これから,次の3つの実行計画を見ていきましょう。
- シーケンシャルアクセスの実行計画
- ランダムアクセスの実行計画
- 結合の実行計画
シーケンシャルアクセスの実行計画
実行計画を確認する手段は実装によって違いますが,多くのDBMSがコマンドラインのインタフェースから確認するためのコマンドを用意しています(表2)。
表2 実行計画を確認するコマンド
| DBMS | 実行計画取得コマンド |
|---|---|
| Oracle(※) | set autotrace traceonly |
| SQL Server(※) | SET SHOWPLAN_TEXT ON |
| DB2 | EXPLAIN ALL WITH SNAPSHOT FOR <SQL文> |
| PostgreSQL | EXPLAIN <SQL文> |
| MySQL | EXPLAIN EXTENDED <SQL文> |
※ OracleおよびSQL Serverでは,上記コマンドのあとに対象のクエリを実行する
具体的に,図2のようなサンプルテーブルを使うことにしましょう。ある業種の店舗についての評価や所在地域のデータを保存するテーブルだと考えてください。主キーは店舗IDで,テーブルには60行のデータを入れ,そのあとに統計情報を取得済みだとします。
図2 店舗テーブル
Shops(店舗)
(店舗ID) | (店舗名) | (評価) | (地域) |
: | |||
まずは,レコードを全件取得するクエリの実行計画を見てみましょう。
SELECT *
FROM Shops;
Oracle,PostgreSQL,MySQLの3つのDBMSで取得した実行計画を掲載します(リスト1~3)。
リスト1 シーケンシャルアクセスの実行計画(Oracle)
実行計画
----------------------------------------------------------
Plan hash value: 2761254732
② ① ③
------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 60 | 1260 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| SHOPS | 60 | 1260 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------
リスト2 シーケンシャルアクセスの実行計画(PostgreSQL)
QUERY PLAN
-------------------------------------------------------
Seq Scan on shops (cost=0.00..1.60 rows=60 width=22)
② ① ③
リスト3 シーケンシャルアクセスの実行計画(MySQL)
② ① ② ③
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | type | possible_keys | key | key_len | re f | rows | filtered | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | Shops | ALL | NULL | NULL | NULL | NULL | 60 | 100.00 | |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------+
出力される情報のフォーマットは完全に同じではありませんが,すべてのDBMSに共通する出力項目があります。それは,「① 対象のオブジェクト」「② オブジェクトの操作」「③ 操作の対象となるレコード数」です。この3つは,それだけ重要な項目だということです。
① 対象オブジェクト
今,対象オブジェクトについて見ると,Oracleは「Name」列,PostgreSQL はon のあと,MySQLは「table」列に,Shopsテーブルが出力されています。今はこの1つしかテーブルを使用していないため迷うことはありませんが,複数のテーブルを結合する場合,どのオブジェクトに対する操作なのかを混同しないように注意してください。
また,この項目にはテーブル以外にも,インデックスやパーティション,シーケンスなど,SQL文でアクセス対象となるオブジェクトなら何でも現れます。
② オブジェクトの操作
オブジェクトに対する操作は,実行計画で最も重要な項目です。Oracleの「Operation」,PostgreSQLの文頭,MySQLの「select_type」および「type」列からわかります。今,3つのDBMSは同じアクセス操作,すなわちシーケンシャルスキャンを実施しています。PostgreSQLのSeq Scanは「シーケンシャルスキャン」の略です。OracleとMySQLの出力情報は,「テーブルのデータを全部読み込みます」ということしか言っていないため,正確にはこれをシーケンシャルスキャンと同義と取るのは,レイヤを混同しています(注4)。PostgreSQLとは,出力レベルが違うということです。しかし実際のところ,テーブルへのフルアクセスにはほとんどの場合,シーケンシャルスキャンが実施されます。
DBMSはランダムスキャンも選択肢として持ちますが,このサンプルではテーブルのレコード数が少なく,またすべてのレコードを選択するクエリを実行していることから,シーケンシャルスキャンが選択されています。
- 注4)
- たとえばOracleで,あるクエリがシーケンシャルアクセスを行っているかどうかを調べるためには,さらに一歩踏み込んで当該クエリのイベント情報まで調べる必要があります。
③ 操作の対象となるレコード数
3つ目の重要な項目は,操作の対象となるレコード数です。これらはいずれも「Rows」の項目に出力されるため,わかりやすいでしょう。結合や集約が入ってくると,1つのクエリを実行するためだけでも,複数の操作が行われます。そうすると,各操作でどれだけのレコードが処理されるかが,クエリ全体の実行コストを把握するために重要になります(注5)。
- データベースの鉄則 1
- 実行計画で重要な項目は,オブジェクト,操作,レコード数の3つ
レコード数はテーブルの実件数と一致するとは限らない
なお,この件数について1つ誤解してほしくないことがあります。それは,この数値が取得されている情報源についてです。先ほど「クエリが処理される流れ」でオプティマイザが実行計画を作る過程を解説した際にも説明したように,オプティマイザはテーブル情報をカタログマネージャから受け取ります。つまり,ここに出ている件数は,統計情報として取得された値がもとになっています。けっして,いま現時点のテーブルの実件数を示すものではないのです。
ためしに,このShopsテーブルから全レコードを削除して(もちろんコミットもして),それから再度実行計画を取得したらどうなるでしょう。実際にやっていただければわかりますが,3つのDBMSすべてについて,やはりRowsの項目には「60」という値が出力されます。オプティマイザは,あくまで統計というメタ情報を頼りにしていて,実テーブルなんか見ていないのです(注6)。
- 注5)
- これに比べて,OracleやPostgreSQLの出力に含まれる実行コスト(cost)という指標は,あくまで筆者の経験上ですが,あまり役に立ちません。一見するとこの数値を減らすことが良いことのように思えますし,それも間違いではないのですが,どういうアルゴリズムで計算されているか不明なうえ,値の大小についても絶対評価ができないからです(たとえば「このクエリはcostが5,000しかないので1秒以内で終わるでしょう」という予測はできない)。
- 注6)
- 「そんな不正確なことしなくても,クエリ実行時にテーブル件数を数えるJIT処理をすればいいのに」と思うかもしれませんが,もし100万件オーダーのレコードが入っていた場合,rowsの値を取りにいくだけでも延々待たされてしまうことになります。

