Each index on a table adds additional overhead for data modifications
because the indexes also need to be maintained as changes are made to
index key columns. In an OLTP environment, excessive indexes on your
tables can be almost as much of a performance issue as missing indexes.
To improve OLTP performance, you should limit the number of indexes on
your tables to only those absolutely needed; you definitely should
eliminate any unnecessary and unused indexes that may be defined on
your tables to eliminate the overhead they introduce.
Fortunately, SQL Server provides a DMV that you can use to identify which indexes in your database are not being used: sys.dm_db_index_usage_stats. The columns in the sys.dm_db_index_usage_stats are shown in Table 1.
Table 1. Columns in the sys.dm_db_index_usage_stats DMV
Column Name | Description |
---|
database_id | ID of the database on which the table or view is defined |
object_id | ID of the table or view on which the index is defined |
index_id | ID of the index |
user_seeks | Number of seeks by user queries |
user_scans | Number of scans by user queries |
user_lookups | Number of bookmark lookups by user queries |
user_updates | Number of updates by user queries |
last_user_seek | Time of last user seek |
last_user_scan | Time of last user scan |
last_user_lookup | Time of last user lookup |
last_user_update | Time of last user update |
system_seeks | Number of seeks by system queries |
system_scans | Number of scans by system queries |
system_lookups | Number of lookups by system queries |
system_updates | Number of updates by system queries |
last_system_seek | Time of last system seek |
last_system_scan | Time of last system scan |
last_system_lookup | Time of last system lookup |
last_system_update | Time of last system update |
Every individual seek, scan, lookup, or update on an
index by a query execution is counted as a use of that index, and the
corresponding counter in the view is incremented. Thus, you can run a
query against this DMV to see whether there are any indexes that your
queries are not using, that is, indexes that either have no rows in the
DMV or have 0 values in the user_seeks, user_scans, or user_lookups columns (or the time values of the last_user_*
columns are significantly in the past). You especially need to focus on
any indexes that don’t show any user query activity but do have a high
value in the last_user_update column. This indicates an index
that’s adding significant update overhead but not being used by any
queries for locating data rows.
For example, the query shown in Listing 1 returns all indexes in the current database that have never been accessed; that is, they would have no records at all in the sys.dm_db_index_usage_stats table.
Listing 1. A Query for Unused Indexes
SELECT convert(varchar(12), OBJECT_SCHEMA_NAME(I.OBJECT_ID)) AS SchemaName, convert(varchar(20), OBJECT_NAME(I.OBJECT_ID)) AS ObjectName, convert(varchar(30), I.NAME) AS IndexName FROM sys.indexes I WHERE -- only get indexes for user created tables OBJECTPROPERTY(I.OBJECT_ID, 'IsUserTable') = 1 -- ignore heaps and I.index_id > 0 -- find all indexes that exist but are NOT used AND NOT EXISTS ( SELECT index_id FROM sys.dm_db_index_usage_stats WHERE OBJECT_ID = I.OBJECT_ID AND I.index_id = index_id AND database_id = DB_ID()) ORDER BY SchemaName, ObjectName, IndexName
|
Also, you should be aware that that the information
is reported in the DMV both for operations caused by user-submitted
queries and for operations caused by internally generated queries, such
as scans for gathering statistics. If you run UPDATE STATISTICS on a table, the sys.dm_db_index_usage_stats table will have a row for each index for the system scan performed by the UPDATE STATISTICS
command. However, the index may still be unused by any queries in your
applications. Consequently, you might want to modify the previous query
to look for indexes with 0 values in the last_user_* columns instead of indexes with no row at all in the DMV. Listing 2 provides an alternative query.
Listing 2. A Query for Indexes Unused by Appliation Queries
SELECT convert(varchar(12), OBJECT_SCHEMA_NAME(I.OBJECT_ID)) AS SchemaName, convert(varchar(20), OBJECT_NAME(I.OBJECT_ID)) AS ObjectName, convert(varchar(30), I.NAME) AS IndexName FROM sys.indexes I LEFT OUTER JOIN sys.dm_db_index_usage_stats u on I.index_id = u.index_id and u.database_id = DB_ID() WHERE -- only get indexes for user created tables OBJECTPROPERTY(I.OBJECT_ID, 'IsUserTable') = 1 -- ignore heaps and I.index_id > 0 -- find all indexes that exist but are NOT used and isnull(u.last_user_seek, 0) = 0 and isnull(u.last_user_scan, 0) = 0 and isnull(u.last_user_lookup, 0) = 0 ORDER BY SchemaName, ObjectName, IndexName
|
Note that the information returned by sys.dm_db_index_usage_stats
is useful only if your server has been running long enough and has
processed a sufficient amount of your standard and peak workflow. Also,
you should be aware that the data in the DMV is cleared each time SQL
Server is restarted, or if a database is detached and reattached. To
prevent losing useful information, you might want to create a scheduled
job that periodically queries the DMVs and saves the information to
your own tables so you can track the information over time for more
thorough and complete analysis.