#48 クエリパフォーマンスの定石を試す(2)

結論
Accessでは「Count(*)」という書き方も『Count(主キーフィールド名)』という書き方はあまり大きな違いはない(定石で言われるほどではない)。ただしフィールド名の部分にインデックスを持たないフィールドを指定すると、確実かつ大幅にパフォーマンスは落ちる。


クエリ(SQL文)のパフォーマンスを上げるための”定石”がAccess(JETデータベースエンジン)でも通じるかを試す2回目、今回は、『Count(*)よりCount(主キーフィールド名)を使う』という定石について調査をしてみました。


まず「Count(*)」という書き方について説明しておきましょう。具体的には「SELECT Count(*) FROM テーブル名」と書くことによって、そのテーブルの総レコード数を取得することができるSQL文です。当然のことながら「SELECT Count(*) FROM テーブル名 WHERE・・・・」と書けばその条件に一致するレコード数を取得することができます。

ここで、一般的には「Count(*)」はテーブル本体のデータ領域を使ってレコード数を求めるとされていますが、「Count(主キーフィールド名)」とすることによって、テーブル本体ではなくインデックスだけを使って集計処理が行われるために処理が高速化される、それがクエリパフォーマンスの定石となっています。つまり、得られる結果は同じだが、主キーフィールドを明示して指定せよということです。

そこで今回は、2つのSQL文の書き方を比較するとともに、さらに主キーでもなく、インデックスも持たない普通のフィールドを指定した3通りについて、その実行時間を比較・検証してみました。


今回の実験では、総レコード数10万件のテーブルを用意しました。主キーフィールドは「受注コード」です。また、「単価」フィールドには一切インデックスは張られていません。
3つのテストパターンそれぞれについて、所定のSQL文を使ってレコードセットを開き(おそらくその時点で3つの違いが時間差となって表れるはずです)、すぐに閉じます。取得されるレコード数は1レコードだけですので特に読み込み後の処理はありません。そしてそのような単にレコードセットを開いて閉じるだけという処理をループで1000回ずつ行ない、その総処理時間と1回当たりの平均処理時間を比較してみます。


テスト用のコードは次のようなものです。OpenRecordsetメソッドの引数に与えるSQL文を除いては3つのテストパターンに違いはありません。

Sub QueryPerformance_2()

  Dim dbs As Database
  Dim rst As Recordset
  Dim strSQL As String
  Dim iintLoop As Integer

  ts_Watch "処理開始", True

  Set dbs = CurrentDb

  For iintLoop = 1 To 1000
    'テストパターン1
    strSQL = "SELECT Count(*) FROM tbl受注明細"
    Set rst = dbs.OpenRecordset(strSQL)
    rst.Close
    ts_Watch "Count(*) "

    'テストパターン2
    strSQL = "SELECT Count(受注コード) FROM tbl受注明細"
    Set rst = dbs.OpenRecordset(strSQL)
    rst.Close
    ts_Watch "Count(主キーフィールド名) "

    'テストパターン3
    strSQL = "SELECT Count(単価) FROM tbl受注明細"
    Set rst = dbs.OpenRecordset(strSQL)
    rst.Close
    ts_Watch "Count(フィールド名) "

  Next iintLoop

End Sub


その結果は次のようになりました。
テストパターン 総実行時間(秒) 平均実行時間(秒)
Count(*) 14.21 0.02
Count(主キーフィールド名) 13.77 0.02
Count(フィールド名) 44.62 0.04

結果を見ていると、まず1000回試行した総実行時間では次のような順位となっています。
  1. Count(主キーフィールド名)
  2. Count(*)
  3. Count(フィールド名)
これを単純にみると確かに”定石”通りであり、『Count(*)よりCount(主キーフィールド名)』は正解かもしれません。しかし1回当たりの平均実行時間ではCount(主キーフィールド名)もCount(*)もまった同じであることが分かります。メモリキャッシュなどの影響でしょうか、1000回繰り返す中には時間がかかる回とそうでない回があります。それらを平均するとどちらも同じということです。よって、一般的な処理を考えれば、「Count(*)」という書き方も「Count(主キーフィールド名)」という書き方はあまり大きな違いはないといってもよいと思います。

はっきりとした記憶はまったくないのですが、以前、「JETデータベースエンジンではCount(*)という書き方は暗黙的にCount(主キーフィールド名)に置き換えられて処理されるので、むしろ主キーフィールド名の変更などの影響を避けるためにもCount(*)の方が間違いがなくてよい」といったような情報を見聞きした覚えがあります。以来、書き方も簡単なのでよくCount(*)を使っていましたが、その情報は100%とは言えないまでも実運用上では正しい情報だったと言っていいかもしれません。


とはいえ、インデックスのない「単価」フィールドを指定した場合だけは明らかに大きな違いが出ています。総実行時間では他の3倍以上、平均実行時間では2倍の時間がかかっています。時間がかかるということだけでなく、何度も試行する際の時間的バラツキも多いようです。
このことから、パフォーマンスの面からは「Count(フィールド名)」でインデックスを持たないフィールドを指定するのは避けるべきということは確か言えることだと思います。
| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved