Friday, July 1, 2011

Identify bad / Unused Indexes

While tuning the performance of a query or Stored Procedure, we might want to first look at the bad and required indexes.

Bad Indexes:- There are indexes that are not being used and are resulting to bad execution plan.
These indexes are noted as Unused Indexes. Below Query helps you to find the unused indexes based on the last Usage Statistics. basically Indexes that are not used for Seek / Scan operations and used under inserts/updates in the Columns are treated as Unused Indexes.


FOR A SINGLE DATABASE

SELECT DB_NAME() AS [Database], o.name AS Table_name, i.name AS index_name, i.type_desc, SUM(s.used_page_count) * 8   AS IndexSizeKB, u.user_seeks, u.user_scans, u.user_lookups, u.user_updates,

u.last_user_seek, u.last_user_scan, 'Drop index ' + i.name + ' on ' + o.name as DropIndexStatement
FROM sys.indexes i JOIN sys.dm_db_partition_stats  AS s ON s.[object_id] = i.[object_id] AND s.index_id = i.index_id
JOIN sys.objects o ON  i.object_id = o.object_id
LEFT JOIN  sys.dm_db_index_usage_stats u ON i.object_id = u.object_id
AND i.index_id = u.index_id AND u.database_id = DB_ID()
WHERE o.type <> 'S' and isnull(u.user_updates,0) > 0and i.type_desc <> 'HEAP' AND U.user_seeks < 100
GROUP BY
o.name , i.name, i.type_desc, u.user_seeks, u.user_scans, u.user_lookups, u.user_updates,
u.last_user_seek, u.last_user_scan, i.name, o.name
ORDER BY
(convert(decimal(19,4),ISNULL(u.user_seeks, 0)) + ISNULL(u.user_scans, 0) + ISNULL(u.user_lookups, 0))/ISNULL(u.user_updates, 0) asc, user_updates desc, o.name, i.name


RUN THIS IF REQUIRED FOR FOR ALL DATABSES
 declare @db_name varchar(100), 
@str varchar(5000) , @str1 varchar(5000)

DECLARE cur_user_db CURSOR FOR 
 SELECT name  
 FROM master..sysdatabases  
 where name not in ('tempdb', 'model', 'master', 'msdb', 'maintenance', 'distribution', 'litespeedlocal', 'litespeedcentral') 
        and databasepropertyex(name,'Updateability') = 'READ_WRITE'  
        and databasepropertyex(name,'Status') = 'ONLINE' 
        and databasepropertyex(name,'Useraccess') = 'MULTI_USER' 

OPEN cur_user_db 

FETCH NEXT FROM cur_user_db into @db_name 
WHILE @@FETCH_STATUS = 0 
BEGIN 
set @str = 'use [' + @db_name + ']' + char(13) + char(10) +
'SELECT DB_NAME() AS [Database], o.name AS Table_name, i.name AS index_name, i.type_desc, SUM(s.used_page_count) * 8   AS IndexSizeKB, u.user_seeks, u.user_scans, u.user_lookups, u.user_updates,
u.last_user_seek, u.last_user_scan, ''Drop index '' + i.name + '' on '' + o.name as DropIndexStatement
FROM sys.indexes i
JOIN sys.dm_db_partition_stats  AS s ON s.[object_id] = i.[object_id] AND s.index_id = i.index_id
JOIN sys.objects o ON  i.object_id = o.object_id
LEFT JOIN  sys.dm_db_index_usage_stats u ON i.object_id = u.object_id         
AND i.index_id = u.index_id AND u.database_id = DB_ID()
WHERE o.type <> ''S'' and isnull(u.user_updates,0) > 0and i.type_desc <> ''HEAP'' AND U.user_seeks < 100
GROUP BY
o.name , i.name, i.type_desc, u.user_seeks, u.user_scans, u.user_lookups, u.user_updates,
u.last_user_seek, u.last_user_scan, i.name, o.name
ORDER BY
(convert(decimal(19,4),ISNULL(u.user_seeks, 0)) + ISNULL(u.user_scans, 0) + ISNULL(u.user_lookups, 0))/ISNULL(u.user_updates, 0) asc, user_updates desc, o.name, i.name'
exec (@str) 
FETCH NEXT FROM cur_user_db into @db_name 
END 
CLOSE cur_user_db 
DEALLOCATE cur_user_db

The Query also provides the Command to drop the Unused Indexes. So its easy to remove them.



...Happy SQLing
Thanks for Visiting and Sharing your Views

2 comments:

  1. Sir, this is good post

    Is there a equivalent for SQL Azure?

    ReplyDelete
  2. Sen - Try this and you might like it
    http://blogs.msdn.com/b/psssql/archive/2011/06/16/tracking-down-missing-indexes-in-sql-azure.aspx

    ReplyDelete