Auto Index Maintenance for All Databases in a SQL Server Instance

Problem

A few months back, I had shared a script to automate index rebuild/reorg in a database with fragmentation analysis inplace at https://www.sherbaz.com/2013/06/resumable-auto-index-rebuild-and-reorganize-script/. But that script would perform the maintenance only on a particular database at a time.

Solution

The script is modified and it now performs maintenance on all indexes across all databases in an instance. This again has two scripts.

1. analyze2.sql – Checks fragmentation level on indexes in all databases of the SQL instance and stores the results in tempdb.

2. defrag2.sql – Based on the data stored in tempdb by analyze2.sql, the script will process the maintenance, either Index rebuild or an index reorganize based on the fragmentation and the @minfrag, @maxfrag settings in defrag.sql script. Indexes fragmented below @minfrag in percentage will be excluded. Indexes fragmented between @minfrag {88dc7f9385b2c8971f38421926506de3e3fc5e843c9520f0674d4b7de6ae62da} and @maxfrag {88dc7f9385b2c8971f38421926506de3e3fc5e843c9520f0674d4b7de6ae62da} will be reorganized. All remaining indexes will be rebuilt.

Scripts are pasted below for your reference. Please let me know if you find some errors, bugs or issues with the code or the method used.

analyze2.sql

DECLARE @dbname AS VARCHAR (200);

DECLARE @tablename AS VARCHAR (255);

DECLARE @indexname AS VARCHAR (255);

DECLARE @sql AS VARCHAR (MAX);

--DECLARE @objectid INT; 
DECLARE @indexid AS VARCHAR (50);

SET NOCOUNT ON;

IF Object_id('tempdb..temptablelist') IS NOT NULL
    DROP TABLE tempdb..temptablelist;

CREATE TABLE tempdb..temptablelist
(
    table_name VARCHAR (100)
);

IF Object_id('tempdb..tempindexlist') IS NOT NULL
    DROP TABLE tempdb..tempindexlist;

CREATE TABLE tempdb..tempindexlist
(
    index_name VARCHAR (100),
    indid      VARCHAR (50) 
);

DECLARE dblist CURSOR
    FOR SELECT name
        FROM   sys.databases
        WHERE  database_id NOT IN (1, 2, 3, 4);

OPEN dblist;

FETCH NEXT FROM dblist INTO @dbname;

WHILE @@FETCH_STATUS = 0
    BEGIN
        IF Object_id('tempdb..[fraglist' + @dbname + ']') IS NOT NULL
            BEGIN
                SET @sql = 'DROP TABLE tempdb..[fraglist' + @dbname + ']';
                EXECUTE (@sql);
            END
        SET @sql = '
            CREATE TABLE tempdb..[fraglist' + @dbname + '] 
              ( 
                 object_id                    VARCHAR(100), 
                 index_id                     VARCHAR(50), 
                 table_name                   VARCHAR(100), 
                 index_name                   VARCHAR(100), 
                 avg_fragmentation_in_percent DECIMAL 
              ) ';
        EXECUTE (@sql);
        -- Declare a cursor.
        SET @sql = 'insert into tempdb..temptablelist
            select SCHEMA_NAME(schema_id)+''.''+name
             from [' + @dbname + '].sys.objects
             where type=''U'' and type_desc = ''USER_TABLE''';
        EXECUTE (@sql);
        DECLARE tables CURSOR
            FOR SELECT table_name
                FROM   tempdb..temptablelist;
        -- Open the cursor. 
        OPEN tables;
        -- Loop through all the tables in the database. 
        FETCH NEXT FROM tables INTO @tablename;
        WHILE @@FETCH_STATUS = 0
            BEGIN;
                SET @sql = 'insert into tempdb..tempindexlist
                    SELECT name,indid FROM [' + @dbname + ']..sysindexes
                     where id=OBJECT_ID(''[' + @dbname + '].' + @tablename + ''')
                     and dpages>0 and indid > 0
                    ';
                --print @sql
                EXECUTE (@sql);
                DECLARE indexes CURSOR
                    FOR SELECT index_name,
                               indid
                        FROM   tempdb..tempindexlist;
                OPEN indexes;
                FETCH NEXT FROM indexes INTO @indexname, @indexid;
                WHILE @@FETCH_STATUS = 0
                    BEGIN
                        -- Do the showcontig of all indexes of the table 
                        SET @sql = '
                        INSERT INTO tempdb..[fraglist' + @dbname + ']
                        SELECT [object_id], 
                               index_id, 
                               ''' + @tablename + ''' AS table_name, 
                               ''' + @indexname + ''' AS index_name, 
                               avg_fragmentation_in_percent 
                        FROM   sys.Dm_db_index_physical_stats(Db_id(''' + @dbname + '''),
                                 Object_id(''' + @dbname + '.' + @tablename + '''), 
                                 ' + @indexid + ', 
                                 NULL, 
                                 ''LIMITED'')
                        ';
                        PRINT @sql;
                        EXECUTE (@sql);
                        FETCH NEXT FROM indexes INTO @indexname, @indexid;
                    END
                TRUNCATE TABLE tempdb..tempindexlist;
                CLOSE indexes;
                DEALLOCATE indexes;
                FETCH NEXT FROM tables INTO @tablename;
            END;
        TRUNCATE TABLE tempdb..temptablelist;
        -- Close and deallocate the cursor. 
        CLOSE tables;
        DEALLOCATE tables;
        SET @sql = 'SELECT * FROM tempdb..[fraglist' + @dbname + ']';
        EXECUTE (@sql);
        FETCH NEXT FROM dblist INTO @dbname;
    END

CLOSE dblist;

DEALLOCATE dblist;


defrag3.sql

DECLARE @maxfrag AS DECIMAL;

DECLARE @minfrag AS DECIMAL;

DECLARE @frag AS DECIMAL;

DECLARE @execstr AS VARCHAR (400);

DECLARE @tablename AS VARCHAR (255);

DECLARE @indexname AS VARCHAR (255);

DECLARE @dbname AS VARCHAR (200);

DECLARE @sql AS VARCHAR (MAX);

SET NOCOUNT ON;

-- Decide on the maximum and minimum fragmentation to allow for rebuild or reorg. fragmentation below minfrag will be ignored 
SELECT @maxfrag = 30.0;

SELECT @minfrag = 4.0;

IF Object_id('tempdb..tempfraglist') IS NOT NULL
    DROP TABLE tempdb..tempfraglist;

CREATE TABLE tempdb..tempfraglist
(
    tablename VARCHAR (100),
    indexname VARCHAR (100),
    frag      DECIMAL      
);

DECLARE dblist CURSOR
    FOR SELECT name
        FROM   sys.databases
        WHERE  database_id NOT IN (1, 2, 3, 4);

OPEN dblist;

FETCH NEXT FROM dblist INTO @dbname;

WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT '--Started Defrag/rebuild...for ' + @dbname + ' Database';
        IF Object_id('tempdb..[fragstatus' + @dbname + ']') IS NOT NULL
            BEGIN
                SET @sql = 'DROP TABLE tempdb..[fragstatus' + @dbname + ']';
                EXECUTE (@sql);
            END
        SET @sql = '
                CREATE TABLE tempdb..[fragstatus' + @dbname + '] 
                  ( 
                        table_name VARCHAR(100), 
                        index_name VARCHAR(100)
                  ) ';
        EXECUTE (@sql);
        SET @sql = 'insert into tempdb..tempfraglist
                    SELECT table_name, 
                             index_name, 
                             avg_fragmentation_in_percent
                      FROM   tempdb..[fraglist' + @dbname + ']
                      WHERE  index_name IS NOT NULL and index_name not in 
                            (select index_name from tempdb..[fragstatus' + @dbname + '])
                             AND avg_fragmentation_in_percent > ' + CAST (@minfrag AS VARCHAR (50));
        EXECUTE (@sql);
        DECLARE fraglist CURSOR
            FOR SELECT tablename,
                       indexname,
                       frag
                FROM   tempdb..tempfraglist;
        OPEN fraglist;
        FETCH NEXT FROM fraglist INTO @tablename, @indexname, @frag;
        WHILE @@FETCH_STATUS = 0
            BEGIN
                IF @frag > @maxfrag
                    BEGIN
                        SELECT @execstr = 'ALTER INDEX [' + @indexname + '] ON
                                                [' + @dbname + '].[' + Replace(@tablename, '.', '].[') + '] 
                            REBUILD WITH 
                            (FILLFACTOR = 80,  SORT_IN_TEMPDB = ON,
                            STATISTICS_NORECOMPUTE = ON, ONLINE = ON)';
                    END;
                ELSE
                    BEGIN
                        SELECT @execstr = 'ALTER INDEX [' + @indexname + '] ON 
                                                [' + @dbname + '].[' + Replace(@tablename, '.', '].[') + ']
                                              REORGANIZE';
                    END
                BEGIN TRY
                    PRINT @execstr;
                    EXECUTE (@execstr);
                END TRY
                BEGIN CATCH
                    SELECT @execstr + ' : Index operation failed.' AS descript,
                           ERROR_NUMBER() AS ErrorNumber,
                           ERROR_SEVERITY() AS ErrorSeverity,
                           ERROR_STATE() AS ErrorState,
                           ERROR_PROCEDURE() AS ErrorProcedure,
                           ERROR_LINE() AS ErrorLine,
                           ERROR_MESSAGE() AS ErrorMessage;
                END CATCH
                --USE THIS TABLE TO VIEW THE ACTIVITY PROGRESS
                SET @sql = 'INSERT INTO tempdb..[fragstatus' + @dbname + ']
                      VALUES      (''' + @tablename + ''', 
                                   ''' + @indexname + ''');';
                --print @sql
                EXECUTE (@sql);
                FETCH NEXT FROM fraglist INTO @tablename, @indexname, @frag;
            END
        TRUNCATE TABLE tempdb..tempfraglist;
        CLOSE fraglist;
        DEALLOCATE fraglist;
        SET @sql = 'SELECT * FROM tempdb..[fragstatus' + @dbname + ']';
        EXECUTE (@sql);
        PRINT '--Defrag/rebuild completed for database: ' + @dbname;
        PRINT '----------------------------------------------------';
        FETCH NEXT FROM dblist INTO @dbname;
    END

CLOSE dblist;

DEALLOCATE dblist;

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.