SQL: defragment,rebuild or show info about indexes in a database

Published by Marian Galie-Andriescu on

This is a sql script I am using quite some time now, from 2010.

It is based on the original work from Microsoft but it is heavily upgraded to give you more options and functionality.

https://msdn.microsoft.com/en-us/library/ms177571.aspx

You can use the script to just show information about the fragmentation level of the indexes, or to give you a list of sql queries to defragment or rebuild the indexes that need it, or to defragment or rebuild the indexes which have a LogicalFrag higher than a specified value. You can even specify a pause in between defragment or rebuild of each index so the sql server load will be better spread in time.

Download the sql script here:

andriescu_sql_defragment_rebuild_indexes_database


-- ******************************************************************************************************
-- * Use this script to get info or defrag or rebuild all indexes in the current database *
-- * M.Galie Andriescu, marian.galie.andriescu@gmail.com *
-- * SQL version 2005 and up *
-- ******************************************************************************************************

DECLARE @maxfrag INT
DECLARE @maxDefragFrag INT
DECLARE @maxRebuildFrag INT
DECLARE @objectid INT
DECLARE @indexname VARCHAR(255)
DECLARE @tablename VARCHAR(255)
DECLARE @logicalfrag DECIMAL
DECLARE @IndexDefragStr NVARCHAR(2000)
DECLARE @IndexDBReindexStr NVARCHAR(2000)
DECLARE @IndexShowContigStr NVARCHAR(2000)
DECLARE @Action Int
DECLARE @fillFactor INT
DECLARE @waitForDelay varchar(9)
DECLARE @allIndexInfo BIT
-- ******************************************************************************************************
-- * Configuration section *
-- ******************************************************************************************************
-- Action can be one of the following:
--
-- -1 : show defrag sql for all indexes with logical fragmentation > @maxDefragFrag
-- -2 : show rebuild sql for all indexes with logical fragmentation > @maxRebuildFrag and using @fillFactor
-- 0 : show information about the indexes
--
-- WARNING: the following settings will defrag indexes without any confirmation asked!
--
-- 1 : defrag all indexes with logical fragmentation > @maxDefragFrag
-- 2 : rebuild all indexes with logical fragmentation > @maxRebuildFrag and using @fillFactor
-- WARNING: only use option 2 with off-line services,
-- in other words stop all application services first!!!
--
-- @waitForDelay = is the time (default 5 seconds) between executing the index de-fragment queries
-- so the sql server will not be used too much.
--
-- @allIndexInfo = 0 Use fast option to return statistics about the existing indexes.
-- This is fast but will not return information about ExtentFrag, CountRows, MinRecSize, AvgRecSize etc
-- = 1 Collect all statistics related to indexes, but this will take a lot more time to process for a big database.
--
-- ******************************************************************************************************
set @Action = 0
set @maxDefragFrag = 20
set @maxRebuildFrag = 80
set @fillFactor = 90
set @waitForDelay = '00:00:05'
set @allIndexInfo = 0
-- ******************************************************************************************************
--drop table ##fraglist
--drop table ##deFragSqlCmd

-- Creating temp table to hold index info
CREATE TABLE ##fraglist
(ObjectName varchar(1024) NULL, IndexName varchar(1024) NULL ,
LogicalFrag decimal(18, 0) NULL , ExtentFrag decimal(18, 0) NULL ,
ObjectId int NULL ,
IndexId int NULL , Lvl int NULL , CountPages int NULL ,
CountRows int NULL , MinRecSize int NULL , MaxRecSize int NULL ,
AvgRecSize int NULL , ForRecCount int NULL , Extents int NULL ,
ExtentSwitches int NULL , AvgFreeBytes int NULL ,
AvgPageDensity int NULL , ScanDensity decimal(18, 0) NULL ,
BestCount int NULL , ActualCount int NULL ,
[Status] INT DEFAULT 0)

CREATE TABLE ##deFragSqlCmd
(ObjectName varchar(255) NULL ,
IndexName varchar(255) NULL ,
LogicalFrag decimal(18, 0) NULL,
DefragSqlCmd NVARCHAR(2000) NULL)

-- Filling the temp table with info
raiserror ('Getting indexes info...', 0,1) with nowait;

IF (@allIndexInfo=1)
BEGIN
--collect all index info, but slow
PRINT 'Collecting all index info...'
INSERT INTO ##fragList(ObjectName,ObjectId,Indexname,IndexID,
Lvl,CountPages,Countrows,MinRecSize,MaxRecsize,
AvgRecSize,ForRecCount,Extents,ExtentSwitches,
AvgFreeBytes,AvgPageDensity,ScanDensity,BestCount,
ActualCount,LogicalFrag,ExtentFrag)
EXEC ('DBCC SHOWCONTIG WITH TABLERESULTS, ALL_INDEXES, NO_INFOMSGS;');
end else
begin
--this is faster, but less info, as for example we will not know ExtentFrag, CountRows, MinRecSize, AvgRecSize etc
PRINT 'Using fast option to collect index info...'
INSERT INTO ##fragList(ObjectName,ObjectId,Indexname,IndexID,
Lvl,CountPages,Countrows,MinRecSize,MaxRecsize,
AvgRecSize,ForRecCount,Extents,ExtentSwitches,
AvgFreeBytes,AvgPageDensity,ScanDensity,BestCount,
ActualCount,LogicalFrag,ExtentFrag)
EXEC ('DBCC SHOWCONTIG WITH TABLERESULTS, ALL_INDEXES, FAST, NO_INFOMSGS;');
end

raiserror ('Finished getting indexes info.', 0,1) with nowait;
-- Removing indexes we are not interested in
-- If you get errors, remove [collate database_default]
delete from ##fraglist where ObjectName collate database_default not in
(select Table_Name from information_schema.tables
where table_type = 'base table') or
indexid IN (0,255)

-- Adding schema name to table name
-- If you get errors, remove [collate database_default]
Update ##fraglist set ObjectName = OBJECT_SCHEMA_NAME(ObjectId) + '.' + RTRIM(LTRIM(ObjectName))

-- Showing index info before defrag
select * from ##fraglist order by logicalFrag desc

-- Perform requested actions
if (@action = 1) or (@action = 2) or (@action = -1) or (@action = -2)
begin
if (@action = 1) or (@action = -1)
set @maxfrag = @maxDefragFrag
else
set @maxfrag = @maxRebuildFrag

-- Create the cursor to loop through index info
DECLARE ind_Cursor CURSOR FOR
SELECT ObjectName, ObjectId, Indexname, LogicalFrag
FROM ##fraglist WHERE LogicalFrag >= @maxfrag AND Status=0

-- Open the cursor
OPEN ind_Cursor

-- Loop through the indexes
FETCH NEXT FROM ind_Cursor INTO @tablename, @objectid, @indexname, @logicalfrag

WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @IndexDefragStr=N'DBCC INDEXDEFRAG (0, '+ QUOTENAME(RTRIM(@tablename),'[') + ',' + QUOTENAME(RTRIM (@indexname)) + ')' + ' WITH NO_INFOMSGS'
SELECT @IndexDBReindexStr=N'DBCC DBREINDEX ('+ QUOTENAME(RTRIM(@tablename),'[') + ',' + QUOTENAME(RTRIM (@indexname)) + ', '+ str(@fillFactor) + ')'
SELECT @IndexShowContigStr=N'DBCC SHOWCONTIG ('+ QUOTENAME(RTRIM(@tablename),'[') + ',' + QUOTENAME(RTRIM (@indexname)) + ')'

if (@action = 1) or (@action = -1)
begin
if (@action = 1)
begin
PRINT 'Start Defrag index = ' + QUOTENAME(RTRIM(@indexname)) + ' from table = ' +
+ QUOTENAME(RTRIM(@tablename)) + ' logical defragmentation is = ' + ltrim(str(@logicalfrag));
EXEC sp_executesql @IndexDefragStr
end else
begin
insert into ##deFragSqlCmd (ObjectName, IndexName, LogicalFrag, DefragSqlCmd)
values (RTRIM(@tablename), RTRIM (@indexname), @logicalfrag, @IndexDefragStr)
end
end else
begin
if (@action = 2)
begin
PRINT 'Start DbReindex index = ' + QUOTENAME(RTRIM(@indexname)) + ' from table = ' +
QUOTENAME(RTRIM(@tablename)) + ' logical defragmentation is = ' + ltrim(str(@logicalfrag));
EXEC sp_executesql @IndexDBReindexStr
end else
begin
insert into ##deFragSqlCmd (ObjectName, IndexName, LogicalFrag, DefragSqlCmd)
values (RTRIM(@tablename), RTRIM (@indexname), @logicalfrag, @IndexDBReindexStr)
end
end

if (@action = 1) or (@action = 2)
begin
EXEC sp_executesql @IndexShowContigStr
WAITFOR DELAY @waitForDelay
end

UPDATE ##fraglist SET Status=1
WHERE Status=0 AND objectName=@tableName AND objectID=@objectID AND IndexName=@indexName

FETCH NEXT FROM ind_Cursor INTO @tablename, @objectid, @indexname, @logicalfrag
END

-- Close and deallocate the cursor
CLOSE ind_Cursor
DEALLOCATE ind_Cursor
END

drop table ##fraglist

if (@action < 0)
begin
select * from ##deFragSqlCmd order by LogicalFrag desc
end
drop table ##deFragSqlCmd

-- ******************************************************************************************************
-- * *
-- * Version Log *
-- * *
-- ******************************************************************************************************
-- * Version 10 *
-- * 21 july 2015 changed the way the table schema is read from the database, now using *
-- * OBJECT_SCHEMA_NAME *
-- * Version 9 *
-- * 2 may 2013 changed fill-factor from 80 to 90 *
-- * Added new parameter allIndexInfo *
-- * Version 8 *
-- * using [database_default] and not [Latin1_General_CI_AS] so as to keep *
-- * the script not independent a certain collation *
-- * Version 7 *
-- * using [collate Latin1_General_CI_AS] *
-- * Version 6 *
-- * 02 dec 2011 : uses temp tables instead of temp DB where it could lead to collation error. *
-- * Version 5 *
-- * 30 nov 2011 : Using option FAST to get info about the indexes. *
-- * : Added delay of 30 seconds between executing a index de-fragmentation. *
-- * Version 4 *
-- * 26 jan 2011 : Added schema to the table name *
-- * Version 3 *
-- * 24 jan 2011 : Added functionality to just print the index defrag sql statements *
-- * Version 2 *
-- * 10 jan 2011 : Removed COLLATE SQL_Latin1_General_CP1_CI_AS from CREATE TABLE ##fraglist *
-- * Version 1 *
-- * 1 aug 2010 : First release. *
-- ******************************************************************************************************

Categories: SQL