SQL ServerのProfiler周りについて調べてみた。

## はじめに

直近SQLServerに関する登壇をする予定ができたので、資料作成のために調べたことを記事にしていきます！

今回はRDSで起動したSQLServerに対して、発行されたSQLなどを確認するSQL Server Profilerの調査をしたので、そのまとめ。

## Profilerって？

以下のようなことでできる便利な機能です！

* クエリの実行状況のモニタリング
* パフォーマンス分析
* セキュリティ監査

## 試してみた！

## 検証環境

今回はローカル環境をあまり汚したくないので、AWSのRDSのサービスを使って確認しています。

![スクリーンショット 2024-11-07 4.55.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/0114b839-254c-2db6-b4b9-1ff3601031c8.png)

### まずはSQL Server Management Studio (SSMS)

現時点で一番簡単にProfilerを使うには、SQL Server Management Studio (SSMS)経由で、SQL Server Profilerを使うのが、楽そうです。

特に設定等の変更は不要で、簡単に利用できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/f538a99c-c16c-5292-3826-21d667aa0704.png)

ただし、２つほど注意が必要です。

#### 1. SSMSは、Windows専用ソフトウェア

SSMSはMac版の提供がありません。

後述しますが、MacユーザはAzuru Data Studioを使う必要があります。

#### 2. SQL Server Profilerの利用は非推奨

下記サイトにある通り、SQL Serverの将来のバージョンで削除される予定です。

https://learn.microsoft.com/ja-jp/sql/tools/sql-server-profiler/sql-server-profiler?view=sql-server-ver16

代替手段として、拡張イベントを使用することを推奨しているようです。

#### 拡張イベントってなに？

https://learn.microsoft.com/ja-jp/sql/relational-databases/extended-events/extended-events?view=sql-server-ver16

AIを駆使しつつ、要約すると以下のような機能です。

* パフォーマンス監視やトラブルシューティングのための軽量な監視システム
* パフォーマンスへの影響が少ない
* 従来のSQL Traceと比べてオーバーヘッドが小さいので、システムリソースの消費が抑えられる
* 柔軟な収集機能があり、以下のような様々なイベントを収集可能
    * T-SQLステートメントの実行
    * デッドロックの発生
    * 待機統計
    * メモリ使用状況
    * などなど
* フィルタリング機能

### Azuru Data Studio

Macユーザは、Azuru Data Studioを利用する必要があります。

https://azure.microsoft.com/ja-jp/products/data-studio

VSCodeに似たUIですが、SSMSに比べ、GUIでできる変更は少ないように見えました。
（ユーザの追加や権限変更がGUIできない？）

![スクリーンショット 2024-11-06 4.06.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/a052be7c-3ce6-f7bf-9213-6367d18df3db.png)


### 拡張ツール「SQL Server Profiler」

Azuru Data Studioで拡張イベントをモニタリングする場合は、拡張ツール「SQL Server Profiler」をインストールする必要があります。

こちらもまだプレビュー版ということで、今後もっと機能が増えていく旨の説明がありました。

![スクリーンショット 2024-11-06 4.09.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/a5269555-c0b8-fd83-a5a6-08e339542506.png)

「SQL Server Profiler」インストール後はサーバの項目を右クリックしてから、起動することができます。

![スクリーンショット 2024-11-06 4.11.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/1712996b-ee07-3296-336d-82a26f676cbd.png)


#### AWS RDSを使う場合の注意点

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/SQLServer.ExtendedEvents.html

上記ページに、RDS上で動作する際の制約が記載されており、この制約の一部が影響し、Azuru Data Studioの標準プロファイルがそのまま利用できないので、少し修正が必要です。

##### 設定画面表示

設定から、`Profiler`と検索し、下記赤枠の`setting.jsonで編集`を選択します。

![スクリーンショット 2024-11-06 4.12.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/0b9597c3-5e1a-1273-07a3-6ba65e833754.png)

##### MEMORY_PARTITION_MODEの変更

`Standard_OnPrem`の`createStatement`内の`MEMORY_PARTITION_MOD`を`PER_CPU`から`NONE`へ変更しておきます。

```diff_json
        {
            "name": "Standard_OnPrem",
            "defaultView": "Standard View",
            "engineTypes": [
                "Standalone"
            ],
-            "createStatement": "CREATE EVENT SESSION [{sessionName}] ON SERVER\n\t\t\t\t\tADD EVENT sqlserver.attention(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.login(SET collect_options_text=(1)\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.logout(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.rpc_completed(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.sql_batch_completed(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.sql_batch_starting(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))\n\t\t\t\t\tADD TARGET package0.ring_buffer(SET max_events_limit=(1000))\n\t\t\t\t\tWITH (EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)"
+            "createStatement": "CREATE EVENT SESSION [{sessionName}] ON SERVER\n\t\t\t\t\tADD EVENT sqlserver.attention(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.login(SET collect_options_text=(1)\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.logout(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)),\n\t\t\t\t\tADD EVENT sqlserver.rpc_completed(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.sql_batch_completed(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),\n\t\t\t\t\tADD EVENT sqlserver.sql_batch_starting(\n\t\t\t\t\t\tACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.client_hostname)\n\t\t\t\t\t\tWHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))\n\t\t\t\t\tADD TARGET package0.ring_buffer(SET max_events_limit=(1000))\n\t\t\t\t\tWITH (EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)"
        },
```

##### 完了

拡張イベントのモニタリングができるようになりました。

![スクリーンショット 2024-11-06 4.20.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/0d3dc33d-53ae-d143-013d-92952ea0734f.png)


### 動作確認

#### フィルタリングしてみる。

初期状態だと、自身の操作以外のログが多く出るので、フィルタリングすると見やすくなります。

例えば、自身のユーザでの操作に絞ると、、

![スクリーンショット 2024-11-06 4.27.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/31a32850-9b71-d70f-30d0-edfeb263b8b0.png)

自身が投入したSQLが見れたりします。

![スクリーンショット 2024-11-06 4.33.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/cde16f70-8e7e-4a95-c8d3-cc249509f14a.png)

フィルタリングはその他、端末名を指す、`hostname`や、アプリケーション名を指す、`ApplicationName`で絞ると特定しやすいかなと思います。

なお、Azuru Data Studioを使っている場合は、ApplocationNameには、`azdata`が設定されてました。

#### ビューの変更

ビューをTuning Viewに変更すると、SQLの実行時間が見やすくなります。
（Durationはマイクロ秒単位です。）

![スクリーンショット 2024-11-06 4.35.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/92c3ce65-ff8e-42d2-d3e4-83e643cdd052.png)

### まとめ

自身の経験は、オンプレ環境のSQLServerにWindows上のSSMS（SQLProfiler）しかなかったので、知識のおさらいのつもりが色々と新しいことを知ることが出来ました。

もう数記事くらい、SQLServer関連の内容を書いていきたいと思います！