Loading

[SQLServer] クエリのパフォーマンス調査・ボトルネックの特定


「前まで早かったんだけど、データが増えてきて遅くなってきた・・・」等、パフォーマンスにまつわる問題は切っても切れない関係になってきています(ノд・。)今回は、SQLServer上でパフォーマンス調査する際に役立つクエリをご紹介します!ボトルネック特定等にお役立て下さい(。・ω・。)

[SQLServer] クエリのパフォーマンス調査・ボトルネックの特定

SQL Serverでクエリのパフォーマンス測定方法

1. 実行プランをONにする

パフォーマンスを考える際は、まずはクエリの実行プランを見ます。実行プランを見て、SQL Serverが意図した形で検索をしているかを確認しましょう。動作下記コマンドを実行することで、実行プランをONにできます。

--# 実行プランのON・OFF方法
SET SHOWPLAN_TEXT ON

通常のSELECTの場合、PKのインデックスが適用されます。

SELECT * FROM table_sample

--# [結果]
SELECT * FROM table_sample WITH(INDEX(idx_table_sample_01,idx_table_sample_02,idx_table_sample_03))  

インデックスを指定してSELECTの場合、指定したインデックスが適用されます。

SELECT * FROM table_sample WITH(INDEX(idx_table_sample_01,idx_table_sample_02,idx_table_sample_03))

--# [結果]
  |--Nested Loops(Inner Join, OUTER REFERENCES:([di1].[dbo].[table_sample_syn].[tr_contract_no], [di1].[dbo].[table_sample_syn].[detail_no]))
       |--Hash Match(Inner Join, HASH:([di1].[dbo].[table_sample_syn].[tr_contract_no], [di1].[dbo].[table_sample_syn].[detail_no])=([di1].[dbo].[table_sample_syn].[tr_contract_no], [di1].[dbo].[table_sample_syn].[detail_no]), RESIDUAL:([di1].[dbo].[table_sample_syn].[tr_contract_no] = [di1].[dbo].[table_sample_syn].[tr_contract_no] AND [di1].[dbo].[table_sample_syn].[detail_no] = [di1].[dbo].[table_sample_syn].[detail_no]))
       |    |--Hash Match(Inner Join, HASH:([di1].[dbo].[table_sample_syn].[tr_contract_no], [di1].[dbo].[table_sample_syn].[detail_no])=([di1].[dbo].[table_sample_syn].[tr_contract_no], [di1].[dbo].[table_sample_syn].[detail_no]), RESIDUAL:([di1].[dbo].[table_sample_syn].[tr_contract_no] = [di1].[dbo].[table_sample_syn].[tr_contract_no] AND [di1].[dbo].[table_sample_syn].[detail_no] = [di1].[dbo].[table_sample_syn].[detail_no]))
       |    |    |--Index Scan(OBJECT:([di1].[dbo].[table_sample_syn].[idx_table_sample_01]))
       |    |    |--Index Scan(OBJECT:([di1].[dbo].[table_sample_syn].[idx_table_sample_02]))
       |    |--Index Scan(OBJECT:([di1].[dbo].[table_sample_syn].[idx_table_sample_03]))
       |--Clustered Index Seek(OBJECT:([di1].[dbo].[table_sample_syn].[table_sample_primary_key]), SEEK:([di1].[dbo].[table_sample_syn].[tr_contract_no]=[di1].[dbo].[table_sample_syn].[tr_contract_no] AND [di1].[dbo].[table_sample_syn].[detail_no]=[di1].[dbo].[table_sample_syn].[detail_no]) LOOKUP ORDERED FORWARD)

まずは、実行クエリが意図した動作になっているかどうかを確認しましょう

2. 使用頻度の少ないインデックスの一覧を取得
--# --------------------------------------------
--# 使用頻度の少ないインデックスの一覧を取得
--# --------------------------------------------
SELECT
  o.name 'テーブルID'
  , i.name 'インデックスID'
  , s.user_seeks 'Index Seek数'
  , s.user_scans 'Index Scan数'
  , s.user_updates 'UPDATE数'
  , s.user_seeks + s.user_scans + s.user_updates '合計'
FROM
  sys.objects o
  INNER JOIN sys.indexes i
    ON
      o.object_id = i.object_id
  INNER JOIN sys.dm_db_index_usage_stats s 
    ON
      i.index_id = s.index_id
WHERE
  o.type = 'U' --# ユーザテーブルを対象
  AND database_id = DB_ID()
ORDER BY
  s.user_seeks + s.user_scans + s.user_updates
3. CPU利用の高いクエリを取得する

CPU利用が高い=計算処理等頭を使う処理が多いと言えます。SQLServerのCPU使用率が100%に近い場合は、下記クエリで原因を特定しましょう。

--# -----------------------------------------------------------
--# CPU利用の多いクエリを取得する
--# -----------------------------------------------------------
SELECT TOP 100
  total_worker_time / execution_count / 1000.0 AS [平均 CPU 時間(ミリ秒)]
  , total_elapsed_time / execution_count / 1000.0 AS [平均実行時間(ミリ秒)]
  , SUBSTRING(
        text
        , (statement_start_offset / 2) + 1
        , (
            (CASE statement_end_offset
                WHEN -1 THEN DATALENGTH(text)
                ELSE statement_end_offset
                END - statement_start_offset
             ) / 2
           ) + 1
        ) AS sql
FROM
  sys.dm_exec_query_stats 
CROSS APPLY
  sys.dm_exec_sql_text(sql_handle)
ORDER BY
4. オブジェクトごとのメモリ使用量を取得

メモリ使用量が多い場合、ほか処理でメモリが利用できず、処理速度が低下します。不要なメモリ使用量を減らし、処理速度を高めましょう。

--# ------------------------------------------------
--# オブジェクトごとのメモリ使用量を取得
--# ------------------------------------------------
SELECT
  DB_NAME (b.database_id) ,OBJECT_NAME(p.object_id) ,p.index_id
  , COUNT(*) As 使用ページ数
FROM
  sys.allocation_units a
  INNER JOIN sys.partitions p
    ON
      a.container_id = p.hobt_id
  INNER JOIN sys.dm_os_buffer_descriptors b
    ON
      a.allocation_unit_id = b.allocation_unit_id
WHERE
  b.database_id = DB_ID() --# 現在接続中の DB のみ
GROUP BY
  b.database_id, p.object_id, p.index_id
ORDER BY
  使用ページ数 DESC
5. アクセスコストが高いテーブルTOP100を取得する(インデックス不足起因)

インデックスが必要な項目にはインデックスを追加して、処理高速化を目指します( • ̀ω•́ )✧

--# ----------------------------------------------------------
--# アクセスコストが高いテーブルTOP100(インデックス不足起因)
--#   等号列:インデックスを張っていないが、WHERE句に指定している列
--#   不等号列:
--#   付加列:インデックスを張る際に、付加列として必要な列
--# ----------------------------------------------------------
SELECT TOP 100
  last_user_seek AS '最後にシークした時間'
  , [Total Cost]  = ROUND(avg_total_user_cost * avg_user_impact *(user_seeks + user_scans),0)
  , avg_user_impact
  , statement AS 'オブジェクト名'
  , equality_columns AS '等号列'
  , inequality_columns AS '不等号列'
  , included_columns   AS '包括列'
  , unique_compiles AS 'コンパイルおよび再コンパイルの数'
  , user_seeks AS 'クエリによって発生したシーク数'
FROM
  sys.dm_db_missing_index_groups mig
  INNER JOIN sys.dm_db_missing_index_group_stats migs
    ON
      migs.group_handle = mig.index_group_handle
  INNER JOIN sys.dm_db_missing_index_details mid
    ON
      mid.index_handle = mig.index_handle
ORDER BY
  [Total Cost] DESC

上記にて取得したレコードに対しては、インデックスを作成しましょう(。・ω・。)

--# 上記でコストが高いものに対して、下記のようにインデックスを張る
CREATE NONCLUSTERED INDEX [インデックス名]
ON [オブジェクト名] ([等号列])
INCLUDE ([包括列を、カンマ区切で列挙])
6. テーブルの断片化率を調査

テーブルの断片化率を調査し、断片化率が高いものはインデックス再構築等を実施しますv(。・ω・。)ィェィ♪

--# --------------------------------------------------------
--# インデックスの断片化率調査
--# --------------------------------------------------------
--# 定数
DECLARE @DBID NVARCHAR(10) = '[データベースのID]'
DECLARE @PERCENT NVARCHAR(10) = '[断片化率]'

--# インデックスの断片化率取得クエリ
SELECT
  DB_NAME(a.database_id)
  , OBJECT_NAME(a.object_id)
  , a.index_type_desc
  , b.name
  , a.fragment_count
  , a.avg_fragmentation_in_percent
FROM
  sys.dm_db_index_physical_stats(DB_ID(@DBID), NULL, NULL, NULL, 'LIMITED') a
  LEFT JOIN sys.indexes b
    ON
      b.object_id = a.object_id
      AND b.index_id = a.index_id
WHERE
  a.avg_fragmentation_in_percent > @PERCENT

上記にて取得したレコードに対しては、インデックスの再構築等を検討しましょう(。・ω・。)

7. 待機中のリソース確認(リアルタイム)

待機中のリソースを確認し、待ちが発生している原因を調査します

--# ------------------------------------
--# 待機中のリソース分析(リアルタイム)
--# ------------------------------------
SELECT
  a.session_id 'セッションID'
  , a.wait_duration_ms '待機時間(ms)'
  , a.wait_type '待機タイプ'
  , b.waiting_tasks_count '総待機発生回数'
  , b.wait_time_ms '総待機時間(ms)'
  , b.max_wait_time_ms '過去最大待機時間(ms)'
  , a.resource_description 'リソース説明'
  , c.name '対象DB(<db-id>:<file-id>:<page-in-file>)'
FROM
  sys.dm_os_waiting_tasks a --# 待機中リソースの確認(リアルタイム)
  LEFT JOIN sys.dm_os_wait_stats b --# 待機中リソースの総計
    ON
      b.wait_type = a.wait_type
  LEFT JOIN sys.databases c --# DB情報
  ON
    c.database_id = LEFT(a.resource_description,1)
WHERE
  1 = 1
--  AND a.wait_type LIKE 'PAGE%LATCH%' --# ページラッチ待ち(Buffer Latch)を取得 
--  AND a.wait_type LIKE 'LATCH%' --# ページラッチ待ち(Non Buffer Latch)を取得 
--  AND a.wait_type LIKE 'WRITELOG' --# ログ書き込み待ちを取得

上記で多く待ちが発生しているものがあれば、待ちの深掘り分析を開始しましょう(。・ω・。)


SQL Server関連記事

SQLServerのその他記事もまとめてますので、是非お立ち寄りください(。・ω・。)

ふぇにば|PhoEniBiR

投稿者プロフィール

わくわくしてて偉い系VTuver ふぇにば です。
少しでもお役に立てておりましたら嬉しいです(。・ω・。)

関連記事

  1. [CSS] CSSだけで手軽にコピー禁止する方法!

    2017.10.11
  2. [CSS] ブラウザ毎にずれないCSS!リセットCSS・デフォルトCSSコード公…

    2018.08.07
  3. [jQuery] formのアクションを変更する〜フォーム送信時(Submit時…

    2018.08.10
  4. [Linux] シェルスクリプトでフォルダ単位圧縮バックアップ & ロ…

    2018.01.09
  5. [jQuery] jQueryでツールチップを自作する方法

    2020.05.17
  6. 『Excel VBA』マクロで辞書オブジェクト(KEY-VALUE)の使い方まと…

    2016.09.01

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

CAPTCHA


New Article

  1. ChatGPT × StableDiffusion
  2. ChatGPT VS コンサルタント
  3. 注目したい5つのテクノロジートレンド2023
PAGE TOP