サーバー カーソル動作とクエリパフォーマンスとの関連性について

皆さん、こんにちは。 SQL Server/Windows Azure SQL Database サポートチームの 高原 です。

今回は、サーバー カーソル (Transact-SQL カーソル) の動作とクエリパフォーマンスとの関連性について説明します。

 

SQL Server では、以下の 4 種類の サーバー カーソル をサポートしています。

  • 静的カーソル (STATIC)
  • キーセット ドリブン カーソル (KEYSET)
  • 動的カーソル (DYNAMIC )
  • 順方向専用カーソル (FAST_FORWARD)

 

各カーソルの動作 (特徴) について簡単に説明します。

 

静的カーソル (STATIC)

・カーソル宣言 (DECLARE CURSOR) 時に指定したクエリ結果セットを、TempDB 上の 一時テーブル に保存

・フェッチ (FETCH) 時に、FIRST、LAST、RELATIVE などのフェッチ動作が使用可能

・取得した結果セットの行の値がカーソル処理中に更新された場合でも、変更は結果セットに反映されない

 

キーセット ドリブン カーソル (KEYSET)

・カーソル宣言 (DECLARE CURSOR) 時に指定したクエリの結果セットの中で、一意となるキー値のみを TempDB 上の 一時テーブル に保存

・取得した結果セットの行のキー値以外のデータがカーソル処理中に更新された場合、変更が結果セットに反映される

・静的カーソル (STATIC) と 動的カーソル (DYNAMIC) の中間的なカーソル

 

動的カーソル (DYNAMIC)

・カーソル宣言 (DECLARE CURSOR) 時に指定したクエリ結果セットを、TempDB 上の 一時テーブル に保存しない

・カーソル処理中に 結果セット内の行に対して行われた変更が全て反映される

 

順方向専用カーソル (FAST_FORWARD)

・カーソル作成時のコストにより、動的カーソル (DYNAMIC) もしくは、静的カーソル (STATIC) のいずれかとして実装される

・動的カーソル として実装された場合は、動的カーソル と同様の動作となる

・静的カーソル として実装された場合は、静的カーソル と同様の動作となる

・フェッチ (FETCH) では、FETCH NEXT のみが使用可能

 

[補足]

カーソル宣言 (DECLARE CURSOR) 時、明示的に サーバー カーソル タイプ などを指定しなかった場合、 まずは動的カーソルとして扱われますが、動的カーソルが作成できない場合には、キーセットや静的カーソルになる可能性があります。

 

 

目的 1

1) カーソル操作中に、結果セット内の行に対して行われた変更を反映される必要がある場合

キーセット ドリブン カーソル (KEYSET) もしくは、動的カーソル (DYNAMIC) を選択する必要があります。

キーセット ドリブン カーソル もしくは、動的カーソル を選択した場合、フェッチ (FETCH) 時にユーザーテーブルに対する参照クエリが実行されます。

そのため、特に 動的カーソルの場合、カーソル宣言時に指定したクエリ内容が複雑な場合、1 回の フェッチ動作の処理に時間を要する可能性があります。

キーセット ドリブン カーソル の場合、TempDB 上の 一時テーブルに保持された キー値を基にして、ユーザー テーブルに対する参照が行われるため、動的カーソル と比較し、フェッチ動作の処理時間を短縮できることが期待できます。

しかしながら、 静的カーソル (STATIC) と比較すると、一般的にフェッチ動作の処理時間が長くなります。

また、キーセット ドリブン カーソル の場合、カーソル処理中に結果セット内のキー値に対する変更が行われたり、行が削除されたとしても、結果セット内のキー値によりフェッチ動作が行われます。

そのため、既に ユーザー テーブル上に存在しない行のフェッチ動作が行われた場合、該当する行をフェッチすることが出来ないため、@@FETCH_STATUS に “-2” のステータスが返されます。

 

目的 2

2) フェッチ (FETCH) 時の処理時間を短くしたい場合

静的カーソル (STATIC) を選択します。

静的カーソル では、カーソル宣言時に指定したクエリの結果セットを、TempDB 上の 一時テーブルにすべて保存します。

そのため、フェッチ動作時には ユーザー テーブル への参照は行われず、一時テーブルに保持された行を順番に取得するだけであるため、一般的に、フェッチ動作の処理時間が短くなります。

しかしながら、カーソル宣言時に指定したクエリの結果セット行数が多い場合、TempDB 上の 一時テーブルにデータを挿入するまでに時間を要したり、TempDB のデータベース物理サイズが大きく拡張される可能性があります。

 

目的 3

3) 1回目のフェッチ (FETCH) 動作までの時間を短くしたい場合

動的カーソル (DYNAMIC) を選択します。

動的カーソル の場合、カーソル宣言時に指定したクエリの結果セットを、TempDB 上の 一時テーブルに保存しないため、即座に 1 回目のフェッチ動作を実施することができます。

 

 

最後に、弊社によくあるお問い合わせの事象と一般的な対処方法を紹介します。

[事例1]

キーセット ドリブン カーソル (KEYSET) 、動的カーソル (DYNAMIC) を 使用したクエリ処理に時間を要する

 

[一般的な対処方法]

キーセット ドリブン カーソル  、動的カーソル の場合、一般的にフェッチ動作がボトルネックとなり、クエリ全体の処理時間が長くなることがあります。

そのため、カーソル処理中に結果セット内の行に対して行われた変更を反映させる必要がない場合は、静的カーソル (STATIC) に変更することにより、クエリ全体の処理時間を短くさせることができる可能性があります。

なお、結果セット内の行に対して行われた変更を反映させる必要がある場合は、カーソル宣言時に指定するクエリを単純化させることにより、クエリ全体の処理時間を短くさせることができる可能性があります。

 

 

[事例2]

複数スレッドから 静的カーソル (STATIC) を使用するクエリを並行して実行した場合、クエリ処理に時間を要する

 

[一般的な対処方法]

静的カーソル では、TempDB への読み込み、書き込み動作が多く行われるため、複数スレッドから並行して、静的カーソルを使用したクエリが実行されると、TempDB への処理がボトルネックとなり、クエリ全体の処理時間が長くなる可能性があります。

そのため、カーソル宣言時 や フェッチ時に時間を要していることが確認できた場合は、1 回の静的カーソルで取得されるデータ量を減らし、処理を分散させることにより、クエリ全体の処理時間を短くさせることができる可能性があります。

また、複数の CPU を搭載している環境の場合、以下の ブログを参照し、TempDB の物理データベースファイル数を増やすことを、検討してみてください。 DO’s&DONT’s #17: やっておいた方がいいこと - tempdb データファイル数を CPU 数に一致させる

 

 

 

[参考情報]

カーソルの種類 (データベース エンジン) DECLARE CURSOR (Transact-SQL) FETCH (Transact-SQL)
※ 本Blogの内容は、2014年5月 現在の内容となっております