identifying unused indexes

  • The views sys.dm_db_index_usage_stats and sys.indexes SEEM to relate on the index_id attribute, but such is not actually the case.

    While the index_id attribute shows the id of the index in the sys.indexes view, in sys.dm_db_index_usage_stats it shows the TYPE of index (HEAP, CLUSTERED etc). You cannot relate these 2 attributes to reveal detailed information about an indexes usage.

    Does anyone have another approach?

    thanks for your help everyone.

  • This is what I use to find unused indexes on my SQL instances:

    If Object_ID('dbo.Indexes_Unused_Select') is Null

    Exec ( 'Create Proc dbo.Indexes_Unused_Select as Select 0')

    Go

    Alter Proc dbo.Indexes_Unused_Select

    @Database sysname = 'ALL'

    ,@Index sysname = 'ALL'

    ,@Min_Row_Count int = 5000

    ,@Table sysname = 'ALL'

    ,@User_scans int = 5

    ,@User_Seeks int = 5

    as

    Set nocount on

    Select @Database = Replace(@Database,'''','''''')

    ,@Index = Replace(@Index,'''','''''')

    ,@Table = Replace(@Table,'''','''''')

    Declare @sql Varchar(Max)

    Declare @Unused_Indexes Table (

    [DB_Name] sysname

    ,Table_name sysname

    ,[Index_Name] sysname null

    ,User_Seeks int

    ,User_Scans int

    ,User_Lookups int

    ,Row_Count int

    ,Used_Page_Count int

    ,Space_Reserved Varchar(50)

    ,[Type] sysname

    )

    DECLARE cr_db CURSOR static FOR

    SELECT [name]

    FROM Master.sys.Databases SD

    WHERE SD.User_Access = 0 --MultiUser

    AND SD.[State] = 0 --Online

    And ([Name] = @Database or @Database = 'ALL')

    open cr_db

    While 0=0

    Begin

    Fetch Next

    From cr_db

    into @Database

    If @@Fetch_Status <> 0

    Break

    begin Try

    Set @sql = 'Use ' + @Database + '

    Select Db_Name() [DB_Name]

    ,Object_name(ius.Object_id) [Name]

    ,si.name [Index_Name]

    ,ius.User_Seeks

    ,ius.User_Scans

    ,ius.User_Lookups

    ,ps.row_count

    ,ps.used_page_count

    ,Case

    When in_row_reserved_page_count * 8 < 2000 Then Cast(in_row_reserved_page_count * 8 as Varchar(20)) + ''KB''

    When in_row_reserved_page_count * 8 / 512 < 5000 Then Cast(in_row_reserved_page_count * 8 / 1024 as Varchar(20)) + ''MB''

    Else Cast(in_row_reserved_page_count * 8 / 1024 /1024 as Varchar(20)) + ''GB''

    End As Space_Reserved

    ,si.type_desc [Type]

    From sys.dm_db_index_usage_stats ius

    Join sys.indexes si

    on ius.Object_id = si.Object_id

    and ius.index_id = si.Index_id

    join sys.dm_db_partition_stats ps

    on si.object_id = ps.object_id

    and si.index_id = ps.index_id

    Where ius.Database_ID = db_id()

    and ( Object_Name(ius.Object_ID) = ''' + @Table + ''' or ''' + @Table + ''' = ''ALL'' )

    and ( si.Name = ''' + @index + ''' or ''' + @index + ''' = ''ALL'' )

    and row_count > ' + Cast(@Min_Row_count as Varchar(20)) + '

    --and object_name(ius.object_id) =

    and (User_scans < ' + Cast(@User_Scans as Varchar(10)) + '

    and user_seeks < ' + Cast(@User_Seeks as Varchar(10)) + ')

    order by DB_Name

    ,[Name]

    ,[Index_Name]'

    Insert @Unused_Indexes

    Exec (@SQL)

    End Try

    Begin Catch

    Print Error_Message()

    Print @Database

    End Catch

    End

  • Hi Toby,

    Thank you for the quick reply.

    I was using a similar approach, but during testing turned up some weird results...

    Here is a little sample which shows my confusion about index_id in which I created a dummy idx, called "idx_never_used"

    select object_id('TAJS_ALERT')

    2105058535

    select name, index_id, type, type_desc from sys.indexes where object_id=2105058535

    name index_id type type_desc

    ----------------------------------------------

    NULL 00HEAP

    PK_TAJS_ALERT 22NONCLUSTERED

    alert 32NONCLUSTERED

    idx_never_used682NONCLUSTERED

    select database_id, index_id from sys.dm_db_index_usage_stats where object_id=2105058535

    database_id index_id

    --------------------

    8 0

  • As samples of joining the two tables together, here is a script by Glenn Barry from his DMV a day series.

    -- Possible Bad NC Indexes (writes > reads)

    SELECT OBJECT_NAME(s.[object_id]) AS [Table Name], i.name AS [Index Name], i.index_id,

    user_updates AS [Total Writes], user_seeks + user_scans + user_lookups AS [Total Reads],

    user_updates - (user_seeks + user_scans + user_lookups) AS [Difference]

    FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK)

    INNER JOIN sys.indexes AS i WITH (NOLOCK)

    ON s.[object_id] = i.[object_id]

    AND i.index_id = s.index_id

    WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1

    AND s.database_id = DB_ID()

    AND user_updates > (user_seeks + user_scans + user_lookups)

    AND i.index_id > 1

    ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC;

    Here is a script I use to determine Index statistic duplication in sys.dm_db_index_usage_stats

    select db_name(s.database_id) as DBNAME, object_name(s.object_id) as ObjectName

    , i.name as IndexName

    ,user_seeks, user_scans,user_lookups,user_updates,system_seeks, system_scans

    ,Row_number() over (partition by s.object_id,s.index_id order by s.index_id) as RowNum

    From sys.dm_db_index_usage_stats s

    Inner Join sys.indexes i

    on i.object_id = s.object_id

    And i.index_id = s.index_id

    And s.database_id = db_id()

    Where objectproperty(s.object_id,'ISMSShipped') = 0

    Group by s.database_id,s.object_id,i.name,s.index_id, user_seeks, user_scans

    ,user_lookups,user_updates,system_seeks, system_scans

    And then another script by Glenn that shows how to find unused indexes.

    -- List unused indexes

    SELECT OBJECT_NAME(i.[object_id]) AS [Table Name], i.name

    FROM sys.indexes AS i

    INNER JOIN sys.objects AS o

    ON i.[object_id] = o.[object_id]

    WHERE i.index_id

    NOT IN (SELECT s.index_id

    FROM sys.dm_db_index_usage_stats AS s

    WHERE s.[object_id] = i.[object_id]

    AND i.index_id = s.index_id

    AND database_id = DB_ID())

    AND o.[type] = 'U'

    ORDER BY OBJECT_NAME(i.[object_id]) ASC;

    You can read more about my script here[/url].

    You can read more about Glenns unused indexes script here.

    And finally the Bad NC Index script here.

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

  • tew (4/22/2010)


    Hi Toby,

    Thank you for the quick reply.

    I was using a similar approach, but during testing turned up some weird results...

    Here is a little sample which shows my confusion about index_id in which I created a dummy idx, called "idx_never_used"

    select object_id('TAJS_ALERT')

    2105058535

    select name, index_id, type, type_desc from sys.indexes where object_id=2105058535

    name index_id type type_desc

    ----------------------------------------------

    NULL 00HEAP

    PK_TAJS_ALERT 22NONCLUSTERED

    alert 32NONCLUSTERED

    idx_never_used682NONCLUSTERED

    select database_id, index_id from sys.dm_db_index_usage_stats where object_id=2105058535

    database_id index_id

    --------------------

    8 0

    That means that your table is in a heap and has no index on it. Index_id of 0 is the table heap without Clustered Index. It typically does not have a name.

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

  • Hi Jason,

    But there are 4 indexes, including the heap, defined on the table (please see the middle query). Yet when I query the dmv with the same object name, information for only one of the indexes on the same object is returned.

    Sorry, the formatting didn't turn out very well.

    I know I didn't run a query that used the index that I defined (idx_never_used), so why isn't it being returned when the dmv is queried?

    thanks again,

    Tim

  • An entry is only created in the metadata, of that view, for an index after the index has been used. If the index has not been used - it won't appear (it's not supposed to appear anyway).

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

  • eureka.... after updating statistics on the table, all expected rows appear in sys.dm_db_index_usage_stats

    Nice to have a breakthrough from time to time.

    Thanks everyone for your help!

    Tim

  • Congrats and you're welcome.

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

Viewing 9 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic. Login to reply