TekRhythm.com
SQL Script to Rebuild Indexes more intelligently
Hi,
I been pretty busy last few weeks, there are instances when you want to create a Intelligent system to understand the underlying databse system and rebuild index based on that :
Here is the one which worked for me :
Step 1 :
Create the below table DbIndexPreferences in Central database, with the same structure.
| Field Name | Data Type | Constraint |
| DatabaseName | Varchar(128) | |
| FillFactor | float | |
| MinFragmentation | float | |
| RebuildThreshold | float |
Step2:
Create a log table IndexDefragLog to store details for defragmentation/Fillfactor
| Field Name | Data Type | Constraint |
| IndexDefrag_id | Int identiry(1,1) | PRIMARY KEY |
| DatabaseID | int | |
| DatabaseName | Nvarchar(128) | |
| ObjectID | int | |
| ObjectName | Nvarchar(128) | |
| IndexID | Int | |
| IndexName | Nvarchar(128) | |
| PartitionNumber | Smallint | |
| Fragmentation | Float | |
| FillFactor | Nvarchar(10) | |
| Page_Count | int | |
| DateTimeStart | Datetime | |
| DurationSeconds | int |
STEP 3:
Create Below procedure :
USE Ur_centralDBName_WhereUDeplpyedTable
GO
CREATE PROCEDURE dbo.USP_ManageIndexes
(
@minFragmentation FLOAT = 5.0
/* in percent, will not defrag if fragmentation less than specified */
, @rebuildThreshold FLOAT = 30.0
/* in percent, greater than @rebuildThreshold will result in rebuild instead of reorg */
, @executeSQL BIT = 1
/* 1 = execute; 0 = print command only */
, @DATABASE VARCHAR(128) = Null
/* Option to specify a database name; null will return all */
, @tableName VARCHAR(4000) = Null -- databaseName.schema.tableName
/* Option to specify a table name; null will return all */
, @onlineRebuild
BIT = 1
/* 1 = online rebuild; 0 = offline rebuild; only in Enterprise */
, @maxDopRestriction TINYINT = Null
/* Option to restrict the number of processors for the operation; only in Enterprise */
, @printCommands BIT = 0
/* 1 = print commands; 0 = do not print commands */
, @printFragmentation BIT = 0
/* 1 = print fragmentation prior to defrag;
0 = do not print */
, @defragDelay CHAR(8) = '00:00:05'
/* time to wait between defrag commands */
, @scanMode NVARCHAR(8) = N'Limited'
/* scan level to be used with dm_db_index_physical_stats. Options are DEFAULT, NULL, LIMITED, SAMPLED, or DETAILED. The default (NULL) is LIMITED */
, @debugMode BIT = 1
/* display some useful comments to help determine if/where issues occur */
, @FillFactor NVARCHAR(100) = '90'
)
AS
SET NOCOUNT ON;
SET XACT_Abort ON;
SET Quoted_Identifier ON;
BEGIN
IF @debugMode = 1 RAISERROR('Script Started...', 0, 42) WITH NoWait;
/* Declare our variables */
DECLARE @objectID INT
, @databaseID INT
, @databaseName NVARCHAR(128)
, @indexID INT
, @partitionCount BIGINT
, @schemaName NVARCHAR(128)
, @objectName NVARCHAR(128)
, @indexName NVARCHAR(128)
, @partitionNumber SMALLINT
, @partitions SMALLINT
, @fragmentation FLOAT
, @pageCount INT
, @sqlCommand NVARCHAR(4000)
, @rebuildCommand NVARCHAR(200)
, @dateTimeStart DATETIME
, @dateTimeEnd DATETIME
, @containsLOB BIT
, @editionCheck BIT
, @debugMessage VARCHAR(128)
, @updateSQL NVARCHAR(4000)
, @partitionSQL NVARCHAR(4000)
, @partitionSQL_Param NVARCHAR(1000)
, @LOB_SQL NVARCHAR(4000)
, @LOB_SQL_Param NVARCHAR(1000)
, @URN NVARCHAR(4000)
, @WithURN INT
, @URN_SQL_Param NVARCHAR(1000)
, @IDENT NVARCHAR(4000)
, @IS_PRI_IDENT INT
, @IDENT_SQL_Param NVARCHAR(1000)
/* Create our temporary tables */
CREATE TABLE #indexDefragList
(
databaseID INT
, databaseName NVARCHAR(128)
, objectID INT
, indexID INT
, partitionNumber SMALLINT
, fragmentation FLOAT
, page_count INT
, defragStatus BIT
, schemaName NVARCHAR(128) Null
, objectName NVARCHAR(128) Null
, indexName NVARCHAR(128) Null
);
CREATE TABLE #databaseList
(
databaseID INT
, databaseName VARCHAR(128)
);
CREATE TABLE #processor
(
[INDEX] INT
, Name VARCHAR(128)
, Internal_Value INT
, Character_Value INT
);
IF @debugMode = 1 RAISERROR('Beginning validation...', 0, 42) WITH NoWait;
/* Just a little validation... */
IF @minFragmentation Not Between 0.00 And 100.0
SET @minFragmentation = 5.0;
IF @rebuildThreshold Not Between 0.00 And 100.0
SET @rebuildThreshold = 30.0;
IF @defragDelay Not Like '00:[0-5][0-9]:[0-5][0-9]'
SET @defragDelay = '00:00:05';
/* Make sure we're not exceeding the number of processors we have available */
INSERT INTO #processor
EXECUTE XP_MSVER 'ProcessorCount';
IF @maxDopRestriction IS Not Null And @maxDopRestriction > (SELECT Internal_Value FROM #processor)
SELECT @maxDopRestriction = Internal_Value
FROM #processor;
/* Check our server version; 1804890536 = Enterprise, 610778273 = Enterprise Evaluation, -2117995310 = Developer */
IF (SELECT SERVERPROPERTY('EditionID')) In (1804890536, 610778273, -2117995310)
SET @editionCheck = 1 -- supports online rebuilds
ELSE
SET @editionCheck = 0; -- does not support online rebuilds
IF @debugMode = 1 RAISERROR('Grabbing a list of our databases...', 0, 42) WITH NoWait;
/* Retrieve the list of databases to investigate */
INSERT INTO #databaseList
SELECT database_id
, name
FROM sys.databases
WHERE name = IsNull(@DATABASE, name)
And database_id > 4 -- exclude system databases
And [STATE] = 0; -- state must be ONLINE
IF @debugMode = 1 RAISERROR('Looping through our list of databases and checking for fragmentation...', 0, 42) WITH NoWait;
/* Loop through our list of databases */
WHILE (SELECT COUNT(*) FROM #databaseList) > 0
BEGIN
SELECT TOP 1 @databaseID = databaseID
FROM #databaseList;
SELECT TOP 1 @databaseName = databaseName
FROM #databaseList;
SELECT @debugMessage = ' working on ' + DB_NAME(@databaseID) + '...';
IF @debugMode = 1
RAISERROR(@debugMessage, 0, 42) WITH NoWait;
/*If the database is present in the DbIndexPreferences then take the parameters values from there else the default*/
IF EXISTS (SELECT 1 FROM DbIndexPreferences WHERE DatabaseName = @databaseName)
BEGIN
SELECT @minFragmentation = isnull(MinFragmentation,@minFragmentation) from DbIndexPreferences where DatabaseName = @databaseName
SELECT @RebuildThreshold = isnull(MinFragmentation,@minFragmentation) from DbIndexPreferences where DatabaseName = @databaseName
END
/* Determine which indexes to defrag using our user-defined parameters */
INSERT INTO #indexDefragList
SELECT
database_id AS databaseID
, QUOTENAME(DB_NAME(database_id)) AS 'databaseName'
, [OBJECT_ID] AS objectID
, index_id AS indexID
, partition_number AS partitionNumber
, avg_fragmentation_in_percent AS fragmentation
, page_count
, 0 AS 'defragStatus' /* 0 = unprocessed, 1 = processed */
, Null AS 'schemaName'
, Null AS 'objectName'
, Null AS 'indexName'
FROM sys.dm_db_index_physical_stats (@databaseID, OBJECT_ID(@tableName), Null , Null, @scanMode)
WHERE avg_fragmentation_in_percent >= @minFragmentation
And index_id > 0 -- ignore heaps
And page_count > 8 -- ignore objects with less than 1 extent
OPTION (MaxDop 1);
DELETE FROM #databaseList
WHERE databaseID = @databaseID;
END
CREATE CLUSTERED INDEX CIX_temp_indexDefragList
ON #indexDefragList(databaseID, objectID, indexID, partitionNumber);
SELECT @debugMessage = 'Looping through our list... there''s ' + CAST(COUNT(*) AS VARCHAR(10)) + ' indexes to defrag!'
FROM #indexDefragList;
IF @debugMode = 1 RAISERROR(@debugMessage, 0, 42) WITH NoWait;
/* Begin our loop for defragging */
WHILE (SELECT COUNT(*) FROM #indexDefragList WHERE defragStatus = 0) > 0
BEGIN
IF @debugMode = 1 RAISERROR(' Picking an index to beat into shape...', 0, 42) WITH NoWait;
/* Grab the most fragmented index first to defrag */
SELECT TOP 1
@objectID = objectID
, @indexID = indexID
, @databaseID = databaseID
, @databaseName = databaseName
, @fragmentation = fragmentation
, @partitionNumber = partitionNumber
, @pageCount = page_count
FROM #indexDefragList
WHERE defragStatus = 0
ORDER BY fragmentation DESC;
IF @debugMode = 1 RAISERROR(' Looking up the specifics for our index...', 0, 42) WITH NoWait;
/* Look up index information */
SELECT @updateSQL = N'Update idl
Set schemaName = QuoteName(s.name)
, objectName = QuoteName(o.name)
, indexName = QuoteName(i.name)
From #indexDefragList As idl
Inner Join ' + @databaseName + '.sys.objects As o
On idl.objectID = o.object_id
Inner Join ' + @databaseName + '.sys.indexes As i
On o.object_id = i.object_id
Inner Join ' + @databaseName + '.sys.schemas As s
On o.schema_id = s.schema_id
Where o.object_id = ' + CAST(@objectID AS VARCHAR(10)) + '
And i.index_id = ' + CAST(@indexID AS VARCHAR(10)) + '
And i.type > 0
And idl.databaseID = ' + CAST(@databaseID AS VARCHAR(10));
EXECUTE SP_EXECUTESQL @updateSQL;
/* Grab our object names */
SELECT @objectName = objectName
, @schemaName = schemaName
, @indexName = indexName
FROM #indexDefragList
WHERE objectID = @objectID
And indexID = @indexID
And databaseID = @databaseID;
IF @debugMode = 1 RAISERROR(' Grabbing the partition count...', 0, 42) WITH NoWait;
/* Determine if the index is partitioned */
SELECT @partitionSQL = 'Select @partitionCount_OUT = Count(*)
From ' + @databaseName + '.sys.partitions
Where object_id = ' + CAST(@objectID AS VARCHAR(10)) + '
And index_id = ' + CAST(@indexID AS VARCHAR(10)) + ';'
, @partitionSQL_Param = '@partitionCount_OUT int OutPut';
EXECUTE SP_EXECUTESQL @partitionSQL, @partitionSQL_Param, @partitionCount_OUT = @partitionCount OUTPUT;
IF @debugMode = 1 RAISERROR(' Seeing if there''s any LOBs to be handled...', 0, 42) WITH NoWait;
/* Determine if the table contains LOBs */
SELECT @LOB_SQL = ' Select Top 1 @containsLOB_OUT = column_id
From ' + @databaseName + '.sys.columns With (NoLock)
Where [object_id] = ' + CAST(@objectID AS VARCHAR(10)) + '
And (system_type_id In (34, 35, 99)
Or max_length = -1);'
/* system_type_id --> 34 = image, 35 = text, 99 = ntext
max_length = -1 --> varbinary(max), varchar(max), nvarchar(max), xml */
, @LOB_SQL_Param = '@containsLOB_OUT int OutPut';
EXECUTE SP_EXECUTESQL @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;
IF @debugMode = 1 RAISERROR(' Building our SQL statements...', 0, 42) WITH NoWait;
/* If there's not a lot of fragmentation, or if we have a LOB, we should reorganize */
IF @fragmentation < @rebuildThreshold Or @containsLOB = 1 Or @partitionCount > 1
BEGIN
SET @sqlCommand = N'Alter Index ' + @indexName + N' On ' + @databaseName + N'.'
+ @schemaName + N'.' + @objectName + N' ReOrganize';
/* If our index is partitioned, we should always reorganize */
IF @partitionCount > 1
SET @sqlCommand = @sqlCommand + N' Partition = '
+ CAST(@partitionNumber AS NVARCHAR(10));
END;
/*If the database is present in the DbIndexPreferences then take the FillFactor value from there else the default*/
IF EXISTS (SELECT 1 FROM DbIndexPreferences WHERE DatabaseName = @databaseName)
BEGIN
SELECT @FillFactor = isnull([FillFactor],@FillFactor) from DbIndexPreferences where DatabaseName = @databaseName
END
SELECT @URN = ' Select Top 1 @WithURN_OUT = column_id
From ' + @databaseName + '.sys.columns a With (NoLock) join ' + @databaseName + '.sys.indexes b With (NoLock) on a.object_id = b.object_id
Where a.[object_id] = ' + CAST(@objectID AS VARCHAR(10)) + 'and b.is_primary_key = 1
And a.name like ''urn'+'%'';'
,@URN_SQL_Param = '@WithURN_OUT int OutPut';
EXECUTE SP_EXECUTESQL @URN,@URN_SQL_Param,@WithURN_OUT = @WithURN OUTPUT;
/* Check if the table has a field starting with Urn% which is part of a primary key index then make the fillfactor 80% */
IF(ISNULL(@WithURN,0) !=0)
BEGIN
SET @FillFactor = '80'
END
ELSE
BEGIN
SET @FillFactor = '75'
END
SELECT @IDENT = ' Select Top 1 @IS_PRI_IDENT_OUT = column_id
From ' + @databaseName + '.sys.columns a With (NoLock) join ' + @databaseName + '.sys.indexes b With (NoLock) on a.object_id = b.object_id
Where a.[object_id] = ' + CAST(@objectID AS VARCHAR(10)) + 'and a.is_identity = 1 and b.is_primary_key = 1
and b.type_desc = ''CLUSTERED'';'
,@IDENT_SQL_Param = '@IS_PRI_IDENT_OUT int OutPut';
EXECUTE SP_EXECUTESQL @IDENT,@IDENT_SQL_Param,@IS_PRI_IDENT_OUT = @IS_PRI_IDENT OUTPUT;
IF(ISNULL(@IS_PRI_IDENT,0) !=0)
BEGIN
SET @FillFactor = '90'
END
SELECT @LOB_SQL = ' Select Top 1 @containsLOB_OUT = column_id
From ' + @databaseName + '.sys.columns With (NoLock)
Where [object_id] = ' + CAST(@objectID AS VARCHAR(10)) + '
And (system_type_id In (34, 35, 99)
Or max_length = -1);'
/* system_type_id --> 34 = image, 35 = text, 99 = ntext
max_length = -1 --> varbinary(max), varchar(max), nvarchar(max), xml */
, @LOB_SQL_Param = '@containsLOB_OUT int OutPut';
EXECUTE SP_EXECUTESQL @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;
/* If the index is heavily fragmented and doesn't contain any partitions or LOB's, rebuild it */
IF @fragmentation >= @rebuildThreshold And IsNull(@containsLOB, 0)!= 1 And @partitionCount BEGIN
/* Set online rebuild options; requires Enterprise Edition */
IF @onlineRebuild = 1 And @editionCheck = 1
SET @rebuildCommand = N' Rebuild With (Online = On, FillFactor = '+@FillFactor;
ELSE
SET @rebuildCommand = N' Rebuild With (Online = Off, FillFactor = '+@FillFactor;
/* Set processor restriction options; requires Enterprise Edition */
IF @maxDopRestriction IS Not Null And @editionCheck = 1
SET @rebuildCommand = @rebuildCommand + N', MaxDop = ' + CAST(@maxDopRestriction AS VARCHAR(2)) + N')';
ELSE
SET @rebuildCommand = @rebuildCommand + N')';
SET @sqlCommand = N'Alter Index ' + @indexName + N' On ' + @databaseName + N'.'
+ @schemaName + N'.' + @objectName + @rebuildCommand;
END;
/* Are we executing the SQL? If so, do it */
IF @executeSQL = 1
BEGIN
IF @debugMode = 1 RAISERROR(' Executing SQL statements...', 0, 42) WITH NoWait;
/* Grab the time for logging purposes */
SET @dateTimeStart = GETDATE();
EXECUTE SP_EXECUTESQL @sqlCommand;
SET @dateTimeEnd = GETDATE();
/* Log our actions */
INSERT INTO dbo.IndexDefragLog
(
databaseID, databaseName, objectID, objectName, indexID, indexName, partitionNumber,
fragmentation, [FillFactor], page_count, dateTimeStart, durationSeconds
)
SELECT
@databaseID, @databaseName, @objectID, @objectName, @indexID, @indexName
, @partitionNumber, @fragmentation, @FillFactor, @pageCount, @dateTimeStart, DATEDIFF(SECOND, @dateTimeStart, @dateTimeEnd);
/* Just a little breather for the server */
WAITFOR Delay @defragDelay;
/* Print if specified to do so */
IF @printCommands = 1
PRINT N'Executed: ' + @sqlCommand;
END
ELSE
/* Looks like we're not executing, just printing the commands */
BEGIN
IF @debugMode = 1 RAISERROR(' Printing SQL statements...', 0, 42) WITH NoWait;
IF @printCommands = 1 PRINT IsNull(@sqlCommand, 'error!');
END
IF @debugMode = 1 RAISERROR(' Updating our index defrag status...', 0, 42) WITH NoWait;
/* Update our index defrag list so we know we've finished with that index */
UPDATE #indexDefragList
SET defragStatus = 1
WHERE databaseID = @databaseID
And objectID = @objectID
And indexID = @indexID
And partitionNumber = @partitionNumber;
END
/* Do we want to output our fragmentation results? */
IF @printFragmentation = 1
BEGIN
IF @debugMode = 1 RAISERROR(' Displaying fragmentation results...', 0, 42) WITH NoWait;
SELECT databaseID, databaseName, objectID, objectName, indexID, indexName,
fragmentation, page_count
FROM #indexDefragList;
END;
/* When everything is said and done, make sure to get rid of our temp table */
DROP TABLE #indexDefragList;
DROP TABLE #databaseList;
DROP TABLE #processor;
IF @debugMode = 1 RAISERROR('DONE!:)', 0, 42) WITH NoWait;
SET NOCOUNT OFF;
RETURN 0
END
STEP 4:
Create a SQL Job to run this once in a day or week as per your preference that executes this procedure
Below is the Logic of the stored procedure USP_ManageIndexes
- Take the list of databases in a temporary table.
- Loop through the database (While loop) and do following
- If the database name is present in the DbIndexPreferences then take the parameters values of MinFragmentation, FillFactor, RebuildThreshold from there else we the default values passed to
the stored procedure. - If the server version in Enterprise it will do an ONLINE rebuild else OFFLINE rebuild for non-enterprise.
- Determine which indexes to defrag using our parameters.
- If there’s not a lot of fragmentation, or if a table has a LOB, it will reorganize the index.
- If the index is heavily fragmented and doesn’t contain any partitions or LOB’s, rebuild it.
- If the database is present in the DbIndexPreferences then take the FillFactor value from there else the default values.
- If the table has a field starting with Urn% which is part of a primary key index then it will make the
fillfactor 80% else 75%. - If the table has a clustered identity primary key index then it will set the fillfactor to 90%.
- If @executeSQL = 1, we will run
the Rebuild/Reorganize script and log the details in IndexDefragLog - If @executeSQL = 0 and @printCommands = 1, it will just print the “Alter Index “statements.
- If @debugMode = 1, it will
print all the comments which will be helpful while tracing an error
Hope this helps
Happy Coding
Old Posts
- January 2012 (2)
- October 2011 (2)
- September 2011 (4)
- August 2011 (5)
- July 2011 (11)
Search By Categories
- ASP.Net (5)
- C# (5)
- CSS (4)
- HTML (4)
- Javascript (11)
- jQuery (7)
- Silverlight (1)
- SQL (6)
- Uncategorized (3)
- VB.Net (1)
Search By Tags




