Automated Index Defrag Script
Filed under: Performance & Tuning, SQL 2008, SQL Tips, T-SQL Scripts
Note: This script has been updated. You can find the latest version here: http://sqlfool.com/2009/06/index-defrag-script-v30/
So after much delay, here’s the latest and greatest version of my index defrag script.
A summary of the changes:
- Added support for centralized execution. Option to defrag indexes for a single database from another database, or for all non-system databases.
- Consolidated Enterprise and Standard versions of the script with new @editionCheck variable.
- Added parametrization for MaxDop restrictions during rebuilds; validates that the value does not exceed the actual number of processors on the server.
- Changed minimum fragmentation default value from 10 to 5 to match BOL recommendations.
- Limited defrags to objects with more than 8 pages.
- Added a debug option to give a little more insight into what’s happening and to assist with troubleshooting.
- Updated logic for handling partitions and LOBs.
And a couple of notes and explanations:
Don’t you know you can just pass NULL to sys.dm_db_index_physical_stats for the DatabaseID?
Yes, I realize you can do this. But I don’t want to defrag the system databases, i.e. tempdb, so I opted to handle it this way instead.
How long will this take?
It depends. I don’t necessarily recommend running it without specifying a database; at least, not unmonitored. You *can* do that, but it could take a while. For example, to run sys.dm_db_index_physical_stats for all databases and tables, totaling 2TB, took me 4.5 hours; that doesn’t even count the actual defrags.
Where should I put this?
It’s up to you. If you have a database for items like centralized maintenance or scratch tables, that may be a good place for it. If you prefer, you could also put this in each individual database and call it locally, too. I would not put this in the master or msdb databases.
This is pretty close to a complete rewrite, so please let me know if you encounter any bugs. And now… the code!
IF Not Exists(SELECT [OBJECT_ID] FROM sys.tables WHERE name = N'dba_indexDefragLog') BEGIN -- Drop Table dbo.dba_indexDefragLog CREATE TABLE dbo.dba_indexDefragLog ( indexDefrag_id INT IDENTITY(1,1) Not Null , databaseID INT Not Null , databaseName NVARCHAR(128) Not Null , objectID INT Not Null , objectName NVARCHAR(128) Not Null , indexID INT Not Null , indexName NVARCHAR(128) Not Null , partitionNumber SMALLINT Not Null , fragmentation FLOAT Not Null , page_count INT Not Null , dateTimeStart DATETIME Not Null , durationSeconds INT Not Null CONSTRAINT PK_indexDefragLog PRIMARY KEY CLUSTERED (indexDefrag_id) ) PRINT 'dba_indexDefragLog Table Created'; END IF OBJECTPROPERTY(OBJECT_ID('dbo.dba_indexDefrag_sp'), N'IsProcedure') = 1 BEGIN DROP PROCEDURE dbo.dba_indexDefrag_sp; PRINT 'Procedure dba_indexDefrag_sp dropped'; END; Go CREATE PROCEDURE dbo.dba_indexDefrag_sp /* Declare Parameters */ @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 */ , @debugMode BIT = 0 /* display some useful comments to help determine if/where issues occur */ AS /********************************************************************************* Name: dba_indexDefrag_sp Author: Michelle Ufford, http://sqlfool.com Purpose: Defrags all indexes for one or more databases Notes: CAUTION: TRANSACTION LOG SIZE MUST BE MONITORED CLOSELY WHEN DEFRAGMENTING. @minFragmentation defaulted to 10%, will not defrag if fragmentation is less than that @rebuildThreshold defaulted to 30% as recommended by Microsoft in BOL; greater than 30% will result in rebuild instead @executeSQL 1 = execute the SQL generated by this proc; 0 = print command only @database Optional, specify specific database name to defrag; If not specified, all non-system databases will be defragged. @tableName Specify if you only want to defrag indexes for a specific table, format = databaseName.schema.tableName; if not specified, all tables will be defragged. @onlineRebuild 1 = online rebuild; 0 = offline rebuild @maxDopRestriction Option to specify a processor limit for index rebuilds @printCommands 1 = print commands to screen; 0 = do not print commands @printFragmentation 1 = print fragmentation to screen; 0 = do not print fragmentation @defragDelay time to wait between defrag commands; gives the server a little time to catch up @debugMode 1 = display debug comments; helps with troubleshooting 0 = do not display debug comments Called by: SQL Agent Job or DBA Date Initials Description ---------------------------------------------------------------------------- 2008-10-27 MFU Initial Release for public consumption 2008-11-17 MFU Added page-count to log table , added @printFragmentation option 2009-03-17 MFU Provided support for centralized execution, , consolidated Enterprise & Standard versions , added @debugMode, @maxDopRestriction , modified LOB and partition logic ********************************************************************************* Exec dbo.dba_indexDefrag_sp @executeSQL = 0 , @minFragmentation = 80 , @printCommands = 1 , @debugMode = 1 , @printFragmentation = 1 , @database = 'AdventureWorks' , @tableName = 'AdventureWorks.Sales.SalesOrderDetail'; *********************************************************************************/ SET NOCOUNT ON; SET XACT_Abort ON; SET Quoted_Identifier ON; BEGIN IF @debugMode = 1 RAISERROR('Dusting off the spiderwebs and starting up...', 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); /* 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 @debugMessage = ' working on ' + DB_NAME(@databaseID) + '...'; IF @debugMode = 1 RAISERROR(@debugMessage, 0, 42) WITH NoWait; /* 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, N'Limited') 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 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 <= 1 BEGIN /* Set online rebuild options; requires Enterprise Edition */ IF @onlineRebuild = 1 And @editionCheck = 1 SET @rebuildCommand = N' Rebuild With (Online = On'; ELSE SET @rebuildCommand = N' Rebuild With (Online = Off'; /* 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.dba_indexDefragLog ( databaseID , databaseName , objectID , objectName , indexID , indexName , partitionNumber , fragmentation , page_count , dateTimeStart , durationSeconds ) SELECT @databaseID , @databaseName , @objectID , @objectName , @indexID , @indexName , @partitionNumber , @fragmentation , @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! Thank you for taking care of your indexes! :)', 0, 42) WITH NoWait; SET NOCOUNT OFF; RETURN 0 END Go
Thanks to my beta testers, @scoinva, @davidmtate, @jdanton, and @SuperCoolMoss!
Special thanks to SCM for keeping on me to finish this.
Happy Defragging!
Michelle
Source: http://sqlfool.com/2009/03/automated-index-defrag-script/
Comments
35 Comments on Automated Index Defrag Script
-
onpnt on
Wed, 25th Mar 2009 7:02 am
-
Wesley Smith on
Wed, 25th Mar 2009 12:44 pm
-
Database Fragmentation with Visual Defrag « Tipsy little box on
Thu, 26th Mar 2009 2:31 am
-
Confluence: Valdis Pukis on
Thu, 26th Mar 2009 3:23 am
-
Michelle Ufford on
Thu, 26th Mar 2009 6:16 am
-
Michelle Ufford on
Thu, 26th Mar 2009 6:18 am
-
SQL SERVER - Automated Index Defragmentation Script « Journey to SQL Authority with Pinal Dave on
Fri, 3rd Apr 2009 10:01 pm
-
Vijaya Kadiala on
Sat, 4th Apr 2009 7:23 am
-
gOODiDEA.NET on
Sat, 4th Apr 2009 7:05 pm
-
Aaron Bertrand on
Mon, 13th Apr 2009 8:00 am
-
John Sansom on
Sun, 19th Apr 2009 4:49 am
-
Michelle Ufford on
Mon, 20th Apr 2009 7:37 am
-
John Sansom on
Mon, 20th Apr 2009 1:11 pm
-
Jason Hale on
Tue, 12th May 2009 7:49 am
-
Michelle Ufford on
Tue, 12th May 2009 9:32 am
-
Jon on
Tue, 2nd Jun 2009 1:17 am
-
Michelle Ufford on
Wed, 3rd Jun 2009 6:34 pm
-
CELIO on
Wed, 17th Jun 2009 4:43 pm
-
Paul on
Fri, 19th Jun 2009 1:47 am
-
Kenneth Urena on
Fri, 19th Jun 2009 8:23 am
-
Michelle Ufford on
Fri, 19th Jun 2009 9:16 am
-
Michelle Ufford on
Fri, 19th Jun 2009 9:16 am
-
Michelle Ufford on
Fri, 19th Jun 2009 9:17 am
-
CELIO on
Fri, 19th Jun 2009 9:22 am
-
Aaron Bertrand on
Fri, 19th Jun 2009 11:26 am
-
gary on
Sun, 21st Jun 2009 12:32 pm
-
gary on
Sun, 21st Jun 2009 12:41 pm
-
Chris on
Fri, 26th Jun 2009 6:26 am
-
Tim on
Wed, 29th Jul 2009 10:27 am
-
Michelle Ufford on
Tue, 4th Aug 2009 6:07 am
-
Tim on
Tue, 4th Aug 2009 7:17 am
-
Ed on
Mon, 21st Sep 2009 10:44 am
-
Tim Shay on
Mon, 14th Dec 2009 12:31 am
-
Michelle Ufford on
Mon, 14th Dec 2009 7:30 am
-
Madhu.G on
Fri, 29th Jan 2010 11:23 am
As always your code is excellent Michelle!
One thing I played around with was changing the @database var so I could pass a comma separated list of DB names. My times to defrag are all over for groups of db’s so it makes it easier over making multiple calls on the proc. Which only means I’m being lazy
Michelle,
I’ve been experimenting with your script, and so far, I like it a lot.
I notice that you look to see if the table has any LOB columns, and if so, you use a ReOrganize instead of a ReBuild. But it’s my understanding that it’s not _table_ that’s of concern, the question is if the _index_ contains any LOB columns. So, your approach is issuing ReOrganize when it could be using ReBuild. (Assuming that I’m not misunderstanding SQL Server or your script)
Taking a look at another index maintenance tool from Lara Rubbelke here:
http://blogs.digineer.com/blogs/larar/archive/2007/07/30/smart-index-defragmentation-for-an-online-world.aspx
gave me some ideas.
I added an indexType VARCHAR(18) not NULL to the #indexDefragList, which I filled via the index_type_desc column of the sys.dm_db_index_physical_stats.
Then, each time through the loop, I fill a new variable: @indexType VARCHAR(18) with the indexType column from #indexDefragList
Finally, I changed the code below “Determine if the table contains LOBs” to the following:
IF @indexType = ‘CLUSTERED INDEX’
BEGIN
SELECT @LOB_SQL = ‘ Select @containsLOB_OUT =
CASE
WHEN EXISTS(SELECT * FROM ‘ + @databaseName + ‘.SYS.COLUMNS c
WHERE c.OBJECT_ID = ‘ + CAST(@objectID AS VARCHAR(10)) + ‘
AND (user_type_id in (34, 35, 99, 241)
OR (user_type_id in (165, 167, 231) AND max_length = -1)))
THEN 1
ELSE 0
END’
, @LOB_SQL_Param = ‘@containsLOB_OUT int OutPut’;
END
IF @indexType = ‘NONCLUSTERED INDEX’
BEGIN
SELECT @LOB_SQL = ‘ Select @containsLOB_OUT =
CASE
WHEN EXISTS(SELECT * FROM ‘ + @databaseName + ‘.SYS.index_columns ic
INNER JOIN ‘ + @databasename + ‘.sys.Columns c
ON ic.Column_ID = c.column_id
AND ic.Object_ID = c.Object_ID
WHERE ic.Object_ID = ‘ + CAST(@objectID AS VARCHAR(10)) + ‘
AND ic.Index_ID = ‘ + CAST(@indexID AS VARCHAR(10)) + ‘
AND (user_type_id in (34, 35, 99, 241)
OR (user_type_id in (165, 167, 231) AND max_length = -1)))
THEN 1
ELSE 0
END’
, @LOB_SQL_Param = ‘@containsLOB_OUT int OutPut’;
END
EXECUTE SP_EXECUTESQL @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;
(I note that Laura is using slightly different methods than you for determining lob columns – I don’t know which is better)
These changes seem to be working well for me, but I haven’t given them too much testing. Hopefully they will be useful to someone.
MSSQL…
Index usage report select objectschemaname(indexes.objectid) ‘.’ objectname(indexes.objectid) as objectName, indexes.name, case when isunique = 1 then ‘UNIQUE ‘ else ” end indexes.typedesc as type, ddius.userseeks, ddius…….
Thanks, Wesley! That’s a great point. I don’t work with LOB’s much in my production data, so I haven’t spent too much time on it in the defrag script. I’ll try to enhance the LOB functionality in the next release.
Thanks, Ted! That’s another great idea, I’ll add it on the list of things to include in the future.
[...] developers who are searching for a good solution to improve database performance. Read her article Automated Index Defragmentation Script to equip yourself with better understanding on how to improve database [...]
Just the scrpit which i am looking for…thank you very much….
Thanks — Vijaya Kadiyala
http://www.dotnetvj.com
Interesting Finds: 2009 04.01~04.05…
Web The Browsers Performance in Dependence of HTML Coding COMET (or Reverse AJAX) based Grid control…
Working with SQLFool’s Index Defrag Script…
Last week I decided to play with SQLFool’s Automated Index Defrag Script and found it very usable and…
Hi, thank you again for the excellent script. This has proved very useful indeed in a number of our environments.
I have come across a very minor bug and hope you do not mind me bringing it to your attention on this post however, I could not locate alternative contact details.
Within the loop for defragging (Titled: /* Begin our loop for defragging */), the variable @containsLOB needs to be initialised or cleared with each pass through the loop, otherwise a prior value can be persisted to a subsequent loop.
Hope my explanation makes sense.
Hi John! Do I mind if you point out a bug? Absolutely not! This is great feedback and will only improve my script. I don’t work with LOB’s much so I didn’t test that part of the script very well, obviously.
I’ve added it to the list of revisions for my next release, which I’ll hopefully get to in the next couple of weeks. Thank you for bringing it to my attention!
You’re welcome. Looking forward to the next release!
I have one minor complaint with the script (after the obligatory praise, of course). If you set the @printCommands bit on, it prints the command after executing it. This should be before the command executes and here’s why. If an error occurs during the operation of the script (as it did with me – related to one of my triggers), you cannot see where the error is occurring.
@Jason Good point! I’ll take a look at making that change in my next revision.
Great script.
Idea, how about adding an option to set the database into ‘Simple’ recovery mode before defragging/rebuilding indexes, and then set it back to ‘Full’, or is that bad practice?
@Jon I’m not an expert on recovery, but there’s several reasons why I wouldn’t want to make that an option. The biggest reason is, index defrag operations could take hours to complete, and you wouldn’t necessary want to be without point-in-time recovery for that long. Also, I believe it could cause several issues, such as breaking replication or causing errors if a backup t-log job tries to run. So yes, in general, I believe it’s bad practice.
Thanks for the suggestion and for reading my blog.
follows the error message that appeared when the script ran. What procedure should I take.
thanks.
dba_indexDefragLog Table Created
Msg 102, Level 15, State 1, Procedure dba_indexDefrag_sp, Line 226
Incorrect syntax near ‘OBJECT_ID’.
This worked great for me at very short notice on a production system. So if ever you decide to doubt your efforts, I’d encourage you to continue! Thanks for your contribution.
Hi,
I think this script is great, but it could be even better if u add a maximum quantity of index to rebuild / reorganize every time that the script get call, and another important add would be add page count range management , then for automated tasks we as DBA can create different maintenance plans based on peak times usage of the database.
I’m actually adding that to the script, and it would be great if I can send it back to you and you can make it available or merge it with the actual changes you are actually doing. If you see is worth.
@Celio what version of SQL Server are you running? What patch are you on?
Thanks, @Paul!
@Kenneth I’ll be releasing a new version in a couple of days, so it’s probably too late to make it into that version. But if you want to send me an e-mail at michelle @ sqlfool dot com, I may incorporate it into the next version. Thank you!
@Michelle I am running in SQL Server 2005 Express Edition. V.9.00.3042.00
Index maintenance : these are not your father’s maintenance plans!…
I have blogged about both of them before ( here and here ), but I can never say enough about how helpful…
Michelle Ufford,
Does the new version of this script is released??
Hi Michelle,
We have share point databases in SQL Server 2005 Enterprise edition in one server and BizTalk Application databases in another Server. Does this script validated to index defrag for Share point databases and BizTalk databases??
Thank You
Looks fantastic!
Anyone get the following error when trying to run it on SQL2005 Ent.?
Procedure dba_indexDefrag_sp dropped
Cannot add rows to sys.sql_dependencies for the stored procedure because it depends on the missing table ‘SP_EXECUTESQL’. The stored procedure will still be created; however, it cannot be successfully executed until the table exists.
Chris
I am getting the same error on a SQL 2008 server :
Cannot add rows to sys.sql_dependencies for the stored procedure because it depends on the missing table ‘SP_EXECUTESQL’. The stored procedure will still be created; however, it cannot be successfully executed until the table exists.
Any help?
@Chris and @Tim – I’m not sure what’s causing that error. My Googling returned no results, although I did see a couple of forum discussions where the advice was “I’m not sure what’s causing that but you can safely ignore the error message.” Does the proc still run for you even after getting the error message? Also, please try upgrading to the latest version and seeing if that resolves the issue.
You can find the latest version here:
http://sqlfool.com/2009/06/index-defrag-script-v30/
Michelle,
I actually replaced ‘SP_EXECUTESQL’ with ’sp_executesql’ and it worked. Hope this helps.
Something to check if you are getting odd errors on sp_ExecuteSQL:
If you are getting errors about sp_ExecuteSQL then check your DB’s Collation settings and you’ll most likley find that it’s a case sensative selection and that in turn can cause probelms if just one of the characters in your SQL code is not the correct case.
Just something to check that I found. The Sample AdventureWorks DB from Microsoft is set to a case senative collation
Awesome script! I’ve been chasing index maintenance manually for a good while, planning to write something just like this… but when you’re the operational, development and support DBA wrapped in one, things like that seem to slip down the to-do list, and you end up wasting sooooo much time chasing the issues later. So it’s really good to find a script like this…well done!
Thank you, Tim! I appreciate the kind feedback.
Nice work, Thank you for the script.
Tell me what you're thinking...
and oh, if you've enjoyed the post, please consider subscribing to my blog. ![]()





