以下のKevinKlineのよく知られたクエリを使用して、未使用のインデックスをチェックしました。外部キーで作成されたいくつかのインデックスは、読み取り統計を返さず、書き込みのみを返します。
これらのインデックスを削除しても100%安全ですか?または、オプティマイザーが挿入や削除などに使用でき、DMVに統計を登録しませんか?もしそうなら、どのようにしてそれらが100%安全に除去できると言うことができますか?
サーバーを2か月間実行しているので、毎月のワークロードサイクルをカバーしたと確信しています。
SELECT o.name
, indexname=i.name
, i.index_id
, reads=user_seeks + user_scans + user_lookups
, writes = user_updates
, rows = (SELECT SUM(p.rows) FROM sys.partitions p WHERE p.index_id = s.index_id AND s.object_id = p.object_id)
, CASE
WHEN s.user_updates < 1 THEN 100
ELSE 1.00 * (s.user_seeks + s.user_scans + s.user_lookups) / s.user_updates
END AS reads_per_write
, 'DROP INDEX ' + QUOTENAME(i.name) + ' ON ' + QUOTENAME(c.name) + '.' + QUOTENAME(OBJECT_NAME(s.object_id)) as 'drop statement'
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i ON i.index_id = s.index_id AND s.object_id = i.object_id
INNER JOIN sys.objects o on s.object_id = o.object_id
INNER JOIN sys.schemas c on o.schema_id = c.schema_id
WHERE OBJECTPROPERTY(s.object_id,'IsUserTable') = 1
AND s.database_id = DB_ID()
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
AND (SELECT SUM(p.rows) FROM sys.partitions p WHERE p.index_id = s.index_id AND s.object_id = p.object_id) > 2000
ORDER BY name, reads
サーバーがその間ずっと稼働していて、DMV統計を不注意にクリアした人がいないことを確認できる限り。これは、データベースがデタッチ+再接続/復元/オフライン+オンライン/自動クローズ+オンラインの場合、またはインデックスが明示的に削除/再作成された場合に発生する可能性があります(DMVは無効化/再構築/再編成の影響を受けません。 SQL Server 2012の場合を除き、現在、再構築によって統計がクリアされます -修正されると思われます-@ MartinSmithに感謝します)。
DMLアクティビティが何らかの理由で(たとえば、他のテーブルで更新を実行するために)読み取り容量でインデックスを使用した場合、これは書き込みではなく読み取りアクティビティとして登録されることを期待する必要があります。表示される書き込みはすべてインデックスのメンテナンスです。
余談ですが、このクエリを作成するためのもう少し効率的な方法があります。 2回参照された相関サブクエリを削除し、不要な結合をsys.objects
とsys.schemas
に削除しました。また、必要に応じてすべての列の前にエイリアスを付ける、予約語を角かっこで囲む、AS 'column alias'
構文を削除するなど、いくつかのマイナーな構文を修正しました。 alias = expression
構文からexpression AS alias
構文に変更したSELECT
リストの途中で混乱することがわかりました。1つを選択して一貫性のあるIMHOにする必要があります。クエリ内の過半数と 私の個人的な好みによる の両方でこれらを変更しました。 :-)
SELECT name = OBJECT_NAME(s.[object_id])
, indexname = i.name
, i.index_id
, reads = s.user_seeks + s.user_scans + s.user_lookups
, writes = s.user_updates
, g.[rows]
, reads_per_write = CASE
WHEN s.user_updates < 1 THEN 100.0
ELSE 1.00 * (s.user_seeks + s.user_scans + s.user_lookups) / s.user_updates
END
, [drop statement] = 'DROP INDEX ' + QUOTENAME(i.name)
+ ' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(s.[object_id]))
+ '.' + QUOTENAME(OBJECT_NAME(s.[object_id]))
FROM sys.dm_db_index_usage_stats AS s
INNER JOIN sys.indexes AS i
ON i.index_id = s.index_id
AND s.object_id = i.object_id
INNER JOIN
(
SELECT [object_id], index_id, [rows] = SUM([rows])
FROM sys.partitions GROUP BY [object_id], index_id
HAVING SUM([rows]) > 2000
) AS g
ON i.[object_id] = g.[object_id]
AND i.index_id = g.index_id
WHERE OBJECTPROPERTY(s.[object_id], 'IsUserTable') = 1
AND s.database_id = DB_ID()
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
ORDER BY name, reads;