トップ回答者
SQLServer2005上でのストアド実行について(応答が遅い)

質問
-
ひとまるといいます。よろしくお願いします。
SQLServerフォーラムで質問させていただいたのですが、こちらで何かわからないかと思い、質問させていただきます。
旧サーバ OS WindowsServer2000 SP4
DB SQLServer2000 Standard Edition SP4
から
新サーバ OS WindowsServer2003 SP2(64-bit)DB SQLServer2005 Standard Edition SP2 (64-bit)
へサーバを移行しようとしています。
旧サーバーのDBをデタッチしてファイルを新サーバにコピー後アタッチすることでデータを移行し、
現在動いているアプリが新サーバーで動くか試したところ、ストアドの返答が遅くなる現象が起きました。SQL Server Profilerのテンプレートに「TSQL_SPs」を選択、デバックモードでアプリを実行し、
ストアド実行中監視していましたが特に処理が遅いという部分はありませんでした。
EventClass項目にSP:Completed、TextData項目に問題のストアド名が表示されてから約5分後にアプリに戻ってきました。またストアド実行の後に、ADOを利用して、あるテーブルから1レコード取得するクエリを実行するのですが
実行後5分以上経っても戻ってきませんでした。DB接続設定は
Public Cn As New ADODB.Connection
Server = "[サーバ名]"
Db = "[データベース名]"
User = "SqlUser"
Passwd = ""
With Cn
.ConnectionTimeout = 600
.Provider = "SQLOLEDB.1"
.Properties("Data Source").Value = Server
.Properties("Initial Catalog").Value = Db
.Properties("USER ID").Value = User
.Properties("Password").Value = Passwd
.Properties("Integrated Security") = "SSPI"
.Properties("Persist Security Info") = False
End WithWindows認証で接続しています。
動きは以下の通りです
トランザクション開始(コード)
Cn.BeginTrans (CnはDB接続変数)
ストアド実行
(コード)
Set wCmd = New ADODB.Command (ADO利用)
wCmd.ActiveConnection = Cn (CnはDB接続変数)
wCmd.CommandText = StrSt (StrStはストアド関数Call文)
wCmd.Execute
Set wCmd = Nothingクエリ実行
(コード)
Set Rec = New ADODB.Recordset (ADO利用)
Str=select * FROM A WITH (NOLOCK) where retu='a'
Rec.Open Str, Cn, adOpenKeyset, adLockReadOnly(中略)
トランザクション終了
(コード)
Cn.CommitTrans (CnはDB接続変数)
クエリ実行時、Microsoft SQL Server Management Studio Expressで同じSQL文を実行するとデータを取得します。利用状況モニタをみてもデッドロックは起きていません。
全てのストアド同じ現象が起きるわけではなく、あるストアドだけで起きます。
(ストアド実行で、問題のストアドの代わりに別のストアドを実行するとSQL Server Profiler上でストアドの実行が終わった後すぐにアプリに戻り、またクエリ実行もすぐデータを返してきました)問題のストアドは単体で(Microsoft SQL Server Management Studio Express上で)実行すると約2分で終了します。
問題のストアドが他のストアドと違うのは、ローカルの一時テーブルを使っていること、カーソルをグローバル宣言していることです。
SQLServerのフォーラムでアドバイスをいただき、いろいろ試してみましたが、問題の解決には至りませんでした。
このような現象が起きる原因として何が考えられるか、どなかたわかる方がいらっしゃいましたら教えてください。
開発ツールはVisualBasic6.0 EnterpriseEdition です。
よろしくお願いします。
回答
-
ひとまる さんからの引用 wsSql = "select * FROM A WITH (NOLOCK) where retu='a' " (*)
wRec.Open wsSql, Cn, adOpenKeyset, adLockReadOnly
If wRec.EOF ThenGoTo On_error
End If
wRec.MoveFirst
Set wRec = NothingEnd Sub
気になった部分が二ヶ所あります。
まず、Recordsetをオープンしている部分ですが、adOpenForwardOnlyにする事はできないでしょうか?
(adOpenForwardOnly以外はカーソルの位置情報を保持するため、基本的に遅いです)
もう一つですが、wRec.MoveFirstを行っていますが、次の行でwRecを破棄しているのでwRec.MoveFirstは不要だと思います。
(元のソースがどうなっているか分からないので、何らかの処理が間に入っているのかも知れませんが)
-
ひとまる さんからの引用 取得されるレコードは常に1レコードであり、また、順読みのみなのでwRec.MoveFirstはいらないので消し、adOpenKeysetもadOpenForwardOnlyにして実行しましたが、結果は同じでした。
常に1レコードであるのであれば、「SELECT TOP 1 * FROM A」のようにして、SQL Serverから返されるレコード数を絞ってみてはどうでしょうか。(ストアドプロシージャの場合、必要なレコード数だけ返すとか)
あと、「Set wRec = New ADODB.Recordset」の次の行に
Code SnippetwRec.CursorLocation = adUseClient
を加えたらどうなりますでしょうか。
もしくは「adUseClient」を「adUseServer」に変更して速度を比較してみてください。
※確かCursorLocaltionをadUseClientに変更したら高速化した記憶があるんですが…昔の事なので定かではないですけど。
-
こんにちは、大久保です。
実はずーっと調べていたんですが、VBから実行したときだけ遅くなる という症状に対して該当しそうな情報がなく、手詰まりなかんじです。
ストアドのあとに実行されるクエリは、ストアドプロシージャの処理によって作られた一時テーブル(とデータ)を利用して実行するので、ストアドを動かさなければクエリの実行時間は0秒で正しいのではないですか?
そこで疑問が生じるということは、ストアドプロシージャで行う処理とクエリの処理は関連性がないんでしょうか?
ストアドプロシージャからはレコードが戻されているのでしょうか?Executeで実行しているだけなので、実行結果をクエリとして受け取ってはいないと思っていたのですが、そこは間違いないですよね?
などなど、状況にまだ不明な点が多く、掲示板のやりとりでできる一般的なアドバイスでは解決できない気もしてきました。
あと原因として考えられそうなこととしては
・Management Studio で実行したとき、「終わった」と思ったタイミングでは実は終了していない
・クライアントとサーバーのネットワーク状況が極端に悪い
ぐらいしか思いつきません…
お役に立てず申し訳ありません。
私の投稿はともかく、CatTail さんには非常に有効なアドバイスをいただいていますので CatTail さんの投稿には回答済みチェックをおつけしたいと思っているのですが、ひとまる さんさえよろしければチェックをお願いいたします。
(ひとまる さんがお忙しいようであれば、後日私がおつけします)
すべての返信
-
こんにちは。フォーラムオペレータ大久保です。
こちら、このスレッドの続きですね。
SQLServer2005上でのストアド実行について(応答が遅い)
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=4138956&SiteID=7
SMSで単体実行すると遅くはならないということですので、原因がデータベースのデタッチ・アタッチ等にあるわけではないのではないかと思いますが、私が前のスレッドから見ていてどうもつかめないのが、本当はどの部分が遅いのか ということです。
2. ストアドプロシージャ実行
ストアドプロシージャ実行終了後約5分後
(意図的に5分待ちではなくアプリに戻ってこない)
3. クエリ実行
5分以上経っても戻らない
↑ のような流れだとおしゃっていらっしゃいましたが、「5分戻ってこない」のは、どのような手段で確認されましたか?
おそらく、モジュールコード上にブレークポイントを置いて、ステップ実行されたのではないかと思いましたが、その認識で間違いないですか?
書いてくださった ストアド実行のコードで言いますと、「wCmd.Execute」の行から次の行に移るタイミングで、
同様にクエリ実行のコードですと、「 Rec.Open Str, Cn, adOpenKeyset, adLockReadOnly」 の行で、マウスカーソルが砂時計(古)のまま5分以上待たされる、ということですよね?
私が理解できない点がもうひとつあって、SMSで実行したときの処理時間が、ストアドでは2分・クエリ実行では0秒 と違いがあるのに、プログラムから実行したときは双方とも5分以上(しかも、クエリ実行のほうが時間がかかっている)ところです。
ご提示いただいたコード以外のなにかがあって、実はそこで時間を要しているんじゃないか という疑いが捨てきれていません。。。
前のスレッドで CatTail さんが「どこがボトルネックになっているか調べてみては」とアドバイスをくださっていますが、まずはそこをはっきりさせたほうがいいような気がします。
# ちなみに、ローカルの一時テーブル って具体的にどんなテーブルですか?
-
こんにちは、ひとまるです。
大久保様、混乱を招く書き方をしてしまい、すいません。
確認の仕方は、
1.wCmd.Execute にブレークポイントを置く
2.リモートデスクトップ接続で新サーバーに接続し、新サーバーにあるSQL Server Profilerを実行。テンプレートに「TSQL_SPs」を選択しトレース開始
3.アプリをデバックモードで実行
4.wCmd.Executeで処理が中断
5.Set wCmd = Nothing にブレークポイントを置く
6.再度実行開始
7.新サーバー上のSQL Server Profilerを監視
8.新サーバー上のSQL Server ProfilerのEventClass項目にSP:Completed、TextData項目に問題のストアド名が表示される
(この表示の前にSQL Server Profiler上にストアドのソースが全て表示され終わっていることもあり、私はこの時点でストアドプロシ-ジャの実行が終わったと判断しています)
9.PC上の時計の時刻を見る
10.Set wCmd = Nothing で処理が中断
11.PC上の時計の時刻を見る
です。
9.から11.までの時間が約5分かかっています。
ストアドプロシージャとクエリ実行に処理時間の違いがあるのは処理内容の違いです。
(ストアドプロシージャはテーブルAを参照しながら別テーブルに更新を行うという処理、
クエリ実行はコードに書いてあるように、あるテーブルからデータを取得する処理です)
ストアドプロシージャの実行コードとクエリ実行コードは同じ処理を双方の書き方で書いたのではなく、
ストアドプロシージャの処理の後にクエリ実行、という意味で書いています。
ストアドプロシージャの実行とクエリ実行の間にはファイル削除と画面のリストオブジェクトにメッセージを書く処理がありますが、
Set wCmd = Nothing からクエリ実行のSet Rec = New ADODB.Recordset までは2、3秒程しかかかりませんでした。
一時テーブルは取得したレコードの複数の項目を見るために、一時テーブルに入れる、
また、EXECLの内容によっては列数が若干増減するのでその解釈に使用する、という使い方をしています。
ストアドプロシージャは複数のストアドプロシージャを呼んでおり、それら複数のストアドプロシ-ジャで同じ一時テーブルを参照することもあります。
何かわかりましたらよろしくお願いします。
-
細かい説明ありがとうございます。
私は、6. から 10. までで5分以上 なのかと思っておりました。
サーバー側でストアドプロシージャの実行が終了(Complete)してから、クライアント(VB6)に制御が帰ってくる(wCmd.Execute 命令が終了する)までが5分以上なんですね。
> ストアドプロシージャは複数のストアドプロシージャを呼んでおり、
一回の wCmd.Execute 命令で複数のストアドプロシージャが動くのでしょうか?
すべて同時に動きますか(非同期)?それとも、ひとつ終了したら次、というように順々に実行されますか(同期)?
ストアドの中からまたストアドを読んでいますか?
それは、SMS で速度確認のための実行をしたときも同じ条件ですか?
ローカルの一時テーブルって、テーブル名の先頭に # が一個だけつくアレですよね?
そのテーブルを、パーマネントテーブルにして試してみることはできますか?
または、グローバル一時テーブル(# を2個)にしてみるとか。
↑ハテナマークがずらっと並んで感じ悪い文章になってます…申し訳ありません。
-
こんにちは、ひとまるです。
wCmd.Execute 命令でストアドを実行し、そのストアドが色々なストアドを呼んでいます。
実行は同期です。
また、速度確認のために実行したときも同じ条件です。
ローカルの一時テーブルはおっしゃる通り、テーブル名の先頭に # が一個だけつくものを指しています。
ローカルの一時テーブルをグローバル一時テーブルにして実行しましたが結果は変わりませんでした。
(パーマネントテーブルは以前試してみたのですが結果は同じでした)
関係あるのかわかりませんが、ストアド実行中、tempdbのデータファイル、ログファイルの領域が拡張しているのか確かめようとMicrosoft SQL Server Management Studio Expressでtempdbのプロパティを見ようとしたのですが、「ロック要求がタイムアウトしました」と表示されました。
これはtempdb全体にロックがかかっているということなのでしょうか?
また、利用状況モニタを見ると、所有者の種類がTRANSACTIONで、同じプロセスIDで同じテーブルに複数のロック情報があります。(=複数行ある)
おそらくロックヒントに「NOLOCK」、「UPDLOCK ROWLOCK」を使用しているためだと思うのですが、これが問題を引き起こしているのでしょうか?
それとカーソルですが、ストアド実行時グローバルカーソル宣言をし、呼んだストアドでもそのカーソルを使用しているのですが、問題の原因となるのでしょうか?
(カーソルは問題のストアドが終了する直前に廃棄(DEALLOCATE)しています)
こちらもとりとめもない形になり申し訳ありません。
同じ処理を関数化したかったので関数化した結果、グルーバルカーソルを複数のストアドで使いまわす、レコードセットが使えないので一時テーブルを使用するといった、無理矢理なやり方で作ってしまいました。
何かお気づきの点がありましたらよろしくお願いします。
-
大久保です。もういちど、問題のポイントを整理しますね。
tempdbの拡張やテーブルロックの状況が遅い原因だとすると、SMSで実行してもVBから実行しても事情は変わらないはずだと思っています。
ですので、VBのフォーラムで継続することに意味があるかなーと思っておりました。
(SQL Server側の設定などではなく、VBから呼ばれることで問題が起きている)
今回、「遅い」と判断されたのは、「VB6 からADO経由でストアドプロシージャを実行すると、SMSで同じストアドプロシージャを実行したときと比べて遅い」からだと思っていたんですが、その点間違いないですか?
もし、SMSで実行してもVBで実行しても同じように遅いんだよ、以前(データベースを移行する前)の環境と比べて遅くなったんだよ、ということであれば、今一度 SQL Server 側に問題があるのかも という視点で話をしたほうがいいように思います。
特に、きのうの投稿でいただいた情報が、SMSで実行したときとVBで実行したときに違いがないのであれば、やっぱりSQL Server 側視点でもう一度考えるべきかと。
# そうであれば、このスレッドを SQL Server フォーラムへ移動することもできます。そのほうが良いアドバイスが集まる気もいたします
-
まず、「遅いから早くしたい」というニーズがあったとき、「どの部分が遅いのか」をはっきりさせる必要があります。
(前のスレッドで CatTail さんがご指摘くださっていますね)
ですので、どこで時間がかかっているのかを切り分けるために、あれこれ確認をさせていただいた次第です。
再度お伺いしますが、同じプロセスIDで同じテーブルに複数のロック情報がある状態は、SMSから実行しても VB から実行しても同じでしょうか?
もしそうであれば、この状態は今回の問題とは無関係と考えていいと思います。
(私個人の予想では、同じなんじゃないかと思います)
VBで実行したときと SMS から実行したときで異なっている状況がどこかにないか、それを突き止めることで問題解決のヒントがでてくるのでは?と思います。
これまでいただいた情報からは異なっている部分が見つけられていないので、まだ判断が難しい状況です。
ちなみに、SMSで実行したときに、ストアドプロシージャの実行が終了した と判断しているタイミングはどこですか?
(SMSでの実行はどういう操作でやってますか?)
-
なるほどー、となると、そこが問題のポイントになりそうですね。
最初にご提示いただいた VB6 のソースコードですが、だいぶ簡略化されてますか?
実際にストアドを実行する命令や、Cn(ADODB.Connection オブジェクト)を作っているタイミング、Openしているタイミングと実際にExecuteしているタイミングがどの程度ズレているのか(Cn がPublic宣言されているので、あちこちで使いまわされているのではないかと想像しています)とか、StrSt の中身などご提示いただくことはできますか?
その前に手元でできることがないか調べてみますが、他に何か状況が異なっている部分がないか、引き続きご確認いただけるとありがたいです。
-
ひとまるです。
VB6のソースコードは簡略化しています。全てを書くのは難しいことと、どこの部分から影響が出ているか確認するために
問題のストアドプロシージャを呼ぶだけのフォームを作りました。
Private Sub Form_Load()
Dim wsExc As String
Dim wCmd As ADODB.Command
Dim wMsg As String
Dim wlDay As Long
Dim wlTim As Long
Dim wRec As ADODB.RecordsetDim wsSql As String
On Error GoTo On_error
wlDay = funcLng(Format(Now, "yyyymmdd"))
wlTim = funcLng(Format(Now, "hhmmss"))wsExc = " [ストアド名] [引数1],[引数2],[引数3],[引数4],[引数5],[引数6],[引数7]" (*)
wsExc = wsExc & ",[引数8],[引数9],[引数10],[引数11]" (*)
(引数は全てストアドに渡すもので、ストアドから何か値をもらうための引数はありません)
Set wCmd = New ADODB.Command
wCmd.ActiveConnection = Cn
wCmd.CommandText = wsExc
wCmd.Execute
Set wCmd = Nothing
Set wRec = Nothing
Set wRec = New ADODB.Recordset
wsSql = "select * FROM A WITH (NOLOCK) where retu='a' " (*)wRec.Open wsSql, Cn, adOpenKeyset, adLockReadOnly
If wRec.EOF ThenGoTo On_error
End If
wRec.MoveFirst
Set wRec = NothingOn_Exit:
Exit SubOn_error:
wMsg = Err.Description
GoTo On_ExitEnd Sub
(*)の部分は実際の内容は見せることができないのでこのような形にしました。
今回はトランザクションはかけませんでした。
処理の流れは、
メイン画面オープン時Cn(ADODB.Connection オブジェクト)を作りオープンします。
メイン画面のメニューを押すとストアドプロシージャを呼ぶだけのフォームが開き、ストアドプロシージャが実行されます。
この状態から問題の現象が起こるまで徐々に処理を加えていくつもりでしたが、この段階で現象が起きました。
何かありましたらよろしくお願いします。
-
ひとまる さんからの引用 wsSql = "select * FROM A WITH (NOLOCK) where retu='a' " (*)
wRec.Open wsSql, Cn, adOpenKeyset, adLockReadOnly
If wRec.EOF ThenGoTo On_error
End If
wRec.MoveFirst
Set wRec = NothingEnd Sub
気になった部分が二ヶ所あります。
まず、Recordsetをオープンしている部分ですが、adOpenForwardOnlyにする事はできないでしょうか?
(adOpenForwardOnly以外はカーソルの位置情報を保持するため、基本的に遅いです)
もう一つですが、wRec.MoveFirstを行っていますが、次の行でwRecを破棄しているのでwRec.MoveFirstは不要だと思います。
(元のソースがどうなっているか分からないので、何らかの処理が間に入っているのかも知れませんが)
-
ひとまるです。
CatTail様貴重なアドバイスありがとうございます。
adOpenKeysetが遅いというのは体感的に遅いと思える程でしょうか?(例えば50~60レコードを取得する場合)
今回の問題とは全く別なのですが、画面に情報を表示する時、確かにMicrosoft SQL Server Management Studio Expressで実行するとすぐ結果が戻ってくるのにVB6上だと遅いことがあります。
前任者のソースを何も吟味することなく真似て作っていた為にadOpenKeysetが何を意味しているのか考えたことがありませんでした。
取得されるレコードは常に1レコードであり、また、順読みのみなのでwRec.MoveFirstはいらないので消し、adOpenKeysetもadOpenForwardOnlyにして実行しましたが、結果は同じでした。
adOpenForwardOnlyは他のプログラムにも反映させたいと思います。
本当にありがとうございました。
-
ひとまる さんからの引用 取得されるレコードは常に1レコードであり、また、順読みのみなのでwRec.MoveFirstはいらないので消し、adOpenKeysetもadOpenForwardOnlyにして実行しましたが、結果は同じでした。
常に1レコードであるのであれば、「SELECT TOP 1 * FROM A」のようにして、SQL Serverから返されるレコード数を絞ってみてはどうでしょうか。(ストアドプロシージャの場合、必要なレコード数だけ返すとか)
あと、「Set wRec = New ADODB.Recordset」の次の行に
Code SnippetwRec.CursorLocation = adUseClient
を加えたらどうなりますでしょうか。
もしくは「adUseClient」を「adUseServer」に変更して速度を比較してみてください。
※確かCursorLocaltionをadUseClientに変更したら高速化した記憶があるんですが…昔の事なので定かではないですけど。
-
こんにちは、ひとまるです。
返事が遅くなり申し訳ありません。
CatTail様のアドバイスのコードを加えて実行しましたが変化ありませんでした。
レコード数を絞る、ということですが、ストアドプロシージャの場合は戻すレコード数がわからないので、難しいです。
新たに気づいたことですが、デバック時wCmd.Executeの部分でブレイクポイントを置き、処理が中断した後Set wCmd = Nothingに実行を移動させる(=ストアドプロシージャ実行しない)と、次の処理であるクエリ実行は0秒で戻ってきました。
クエリ実行部分の処理時間(クエリ実行からソースに処理が戻るまでの時間)は問題のストアドプロシージャを実行する、しないに影響されているようです。
(以前にも書いたように、問題のストアドプロシージャを別のストアドプロシージャに置き換えるとストアドプロシージャを実行しても次のクエリ実行時間は0秒です)
けれど利用状況モニタを見てもデッドロックは起きていません。
これはvb6とSQLServerのつながりが、問題のストアドプロシージャの実行によって何らかの影響を与えられている、ということなのでしょうか?
-
こんにちは、大久保です。
実はずーっと調べていたんですが、VBから実行したときだけ遅くなる という症状に対して該当しそうな情報がなく、手詰まりなかんじです。
ストアドのあとに実行されるクエリは、ストアドプロシージャの処理によって作られた一時テーブル(とデータ)を利用して実行するので、ストアドを動かさなければクエリの実行時間は0秒で正しいのではないですか?
そこで疑問が生じるということは、ストアドプロシージャで行う処理とクエリの処理は関連性がないんでしょうか?
ストアドプロシージャからはレコードが戻されているのでしょうか?Executeで実行しているだけなので、実行結果をクエリとして受け取ってはいないと思っていたのですが、そこは間違いないですよね?
などなど、状況にまだ不明な点が多く、掲示板のやりとりでできる一般的なアドバイスでは解決できない気もしてきました。
あと原因として考えられそうなこととしては
・Management Studio で実行したとき、「終わった」と思ったタイミングでは実は終了していない
・クライアントとサーバーのネットワーク状況が極端に悪い
ぐらいしか思いつきません…
お役に立てず申し訳ありません。
私の投稿はともかく、CatTail さんには非常に有効なアドバイスをいただいていますので CatTail さんの投稿には回答済みチェックをおつけしたいと思っているのですが、ひとまる さんさえよろしければチェックをお願いいたします。
(ひとまる さんがお忙しいようであれば、後日私がおつけします)
-