web-dev-qa-db-ja.com

高価なクエリが拡張イベントトレースに表示されない

クエリがアクティビティモニターの負荷の高いクエリリストに表示され、1分あたりの実行回数が非常に多いと報告している顧客がいます。クエリはFETCH API_CURSOR000000000005268Cステートメントです。クエリが実行されると、コストの高いクエリリストにFETCH API_CURSORクエリが表示され、SQLサービスを再起動するまで、SQLサーバーのパフォーマンスは徐々に低下します。 1分あたりの実行は変動しますが、1分あたり10万から数百万の実行になる可能性があります。

場合によっては、この状態が発生し始める前に数か月間実行されることもあれば、数日おきに発生することもあります。

Sp_statement_completedとcursor_executeの両方のイベントをキャプチャする拡張イベントセッションを使用することで、カーソルを実行するステートメントを特定できると思います。彼らのITディレクターに、サーバー上で拡張イベントセッションをセットアップしてもらいました。ある場合には、カーソルの2つの実行をキャプチャしましたが、拡張イベントセッションの実行中にITディレクターが高価なクエリリストの1分あたりの実行が増加しているにもかかわらず、他のすべてのトレースは一致するカーソルの実行を示しません。

とにかく、ステートメントがセッショントレースに表示されませんか?カーソルが実際に実行しているステートメントを特定するより良い方法はありますか?拡張イベントセッションを使用する前に、このステートメントを実行してカーソルを実行している接続/セッションを特定しようとしましたが、結果が返されませんでした。

SELECT t.text, c.session_id
FROM   sys.dm_exec_connections c
        CROSS APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) t
WHERE 1=1
AND t.text LIKE ('API_CURSOR000000000005268C')

高価なクエリリストの画面キャプチャの例を次に示します。 enter image description here

彼らはMS SQL Server 2012 SP1(11.0.3128)を実行しています。任意の提案やポインタがいただければ幸いです。

私のXEセッションはかなり広く開かれています。これが私が彼らに実行させたスクリプトです。

CREATE EVENT SESSION [TestSharpeSoft] ON SERVER 
ADD EVENT sqlserver.cursor_execute(
    ACTION(sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text)),
ADD EVENT sqlserver.error_reported(
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.query_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.module_end(SET collect_statement=(1)
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.query_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.rpc_completed(
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.query_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sp_statement_completed(SET collect_object_name=(1),collect_statement=(1)
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.query_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_statement_completed(
    ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))) 
ADD TARGET package0.event_file(SET filename=N'D:\Temp\Sharpe\Trace\TestSharpeSoft'),
ADD TARGET package0.ring_buffer
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)

もう1つ注意してください。このアプリケーションは数百の場所で実行されており、これが問題を報告している唯一の顧客です。

2
Rick-UpperPark

カーソルの追跡は注意が必要です。ジョーは彼のブログ投稿でそれについて話します FETCH API_CURSORとsp_cursorfetchの起源を突き止める

SELECT c.session_id
    ,es.program_name
    ,es.login_name
    ,es.Host_name
    ,DB_NAME(es.database_id) AS DatabaseName
    ,c.properties
    ,c.creation_time
    ,c.is_open
    ,t.TEXT
FROM sys.dm_exec_cursors(0) c
LEFT JOIN sys.dm_exec_sessions AS es ON c.session_id = es.session_id
CROSS APPLY sys.dm_exec_sql_text(c.sql_handle) t

または、 sp_whoisactive @get_full_inner_text = 1 そして、後で分析するために物理テーブルにログインできます。

3
Kin Shah

私のコメントを答えにして、いくつかの考えから始めましょう。

「FETCH_API_CURSOR」は、サーバー側カーソルを使用してデータを取得するアプリケーションの一部です。これは、一部のAPIがサーバー側のカーソルをSQLに送信するときに発生します。コマンド here について詳しく知ることができます。

おそらく、ここでは高額な、または上位のクエリ拡張イベントを実行しています。これを行うと、最小費用のしきい値を超えるクエリのみが表示されます。したがって、このフェッチの準備は表示されず、フェッチのみが表示されます。

一部のクエリは、送信されるクエリの数に応じてフェッチできるため、他よりもコストがかかります。

ワイドオープンフィルターを使用する必要があり、コストが高くなる可能性があるため、トレースまたは拡張イベントを介してキャプチャすることは非常に困難です。実行中またはセッション中のクエリをキャッチできる場合は、sys.dm_exec_cursors DMVを使用して、実行中のカーソルの詳細を確認できます。このDMVはSESSION_IDを入力引数として受け入れ、有用な情報を返します。これをクエリの実行に関する他のさまざまなDMVと結合して、詳細を確認できます。

これらがどこから来たのかがわかっている場合は、そのアプリケーションまたはアプリケーションが実行されているホストのみをフィルタリングするか、ノイズの少ない開発環境で実行することもできます。

1
Mike Walsh

これは単なる情報であり、テストでは機能したことが示されましたが、2012ではテストしていません(SQL Server 2016のみが現在インストールされています)

以下のXEセッションがSQL Server 2012でも動作することを確認しました。

テストスクリプト

私は偶然出会いました この記事FETCH API_CURSORxxxx呼び出しの発生を再現できるVBScriptを示すPinal DaveによるこれをPowerShellスクリプトに変更して、ローカルインスタンスに対して実行するだけにしました。

拡張イベント

Microsoftは、各バージョン(および場合によってはSP)で拡張イベントに新しいイベントを追加しています。カーソルの原因となっているクエリをキャプチャするために関心のあるイベントは、sqlserver.cursor_closeまたはsqlserver.cursor_openです。 sqlserver.cursor_executeを含めてみましたが、テストでは何も表示されませんでした。これらの各イベントに特定のアクションを追加し、cursor_executeを使用する試みを取り除きました。 XEセッション定義は次のようになります。

CREATE EVENT SESSION [Track_api_cursor] ON SERVER 
ADD EVENT sqlserver.cursor_close(
    ACTION(sqlserver.database_name,sqlserver.is_system,sqlserver.query_hash,sqlserver.session_id,sqlserver.sql_text)
    WHERE ([sqlserver].[is_system]=(0))),
ADD EVENT sqlserver.cursor_open(
    ACTION(sqlserver.database_name,sqlserver.is_system,sqlserver.query_hash,sqlserver.session_id,sqlserver.sql_text)
    WHERE ([sqlserver].[is_system]=(0)))
ADD TARGET package0.ring_buffer
WITH (MAX_DISPATCH_LATENCY=12 SECONDS,TRACK_CAUSALITY=ON)
GO

enter image description here

1
user507