Benchmark IT Consulting Rotating Header Image

“Smart” Reindexing (SQL Server 2005, 2008)

Something that still baffles me about SQL Server is that even in SQL Server 2008 there is no maintenance plan option to do reindexing properly. The key word in that sentence is properly. SQL Server gives you an all or nothing approach to indexing. You have 2 options:

1) Rebuild Indexes

2) Reorganize Indexes

(Great article on the differences between the two : http://decipherinfosys.wordpress.com/2008/02/07/index-rebuilding-vs-index-reorganizing-in-sql-server-2005/ )

If you were to do this through the wizard you can choose individual objects and there are plenty of options to customize but where this falls down is that it does not take into account whether or not an index needs maintenance performed on it.

In SQL Server 2005 DMVs (Dynamic Management Views) were introduced that gave instant access to alot of vital information that prior to SQL Server 2005 wasn’t available or required separate process to gather (anyone remember DBCC SHOWCONTIG?).

Let’s talk about the DMV sys.dm_db_index_physical_stats. “Returns size and fragmentation information for the data and indexes of the specified table or view.” So you would think that with this information readily available that the maintenance plan would have an option to do only the indexes that needed maintenance? nope.

Below is a script that I have been working with for SQL Server 2005 and SQL Server 2008. Because SQL Server 2000 did not have DMVs this script will not work (I have a different script for SQL Server 2000 that I’ll post another time)

The long and the short of what this script does is that it will utilize the sys.dm_db_index_physical_stats DMV and dynamically determine if an index requires maintenance and performs the maintenace.

Variables that require attention
@FragThreshold – This integer value is the minimum percentage that an index is fragmented before maintenance is done

@DoOfflineToo – This bit value indicates whether or not indexes that need to be done offline are done at the same time. This is important because if an index needs to be maintained offline this requires significant locks to the object. (Also please be aware that online index operations are only available on Enterprise or Developer Edition)

This script needs to be run in the context of the database that you are looking to maintain. I usually create an SSIS package and wrap this script in a ForEach loop container that cycles through all the databases but for ease of “blogging” we’ll look at it from a more manual approach.

The “sizzle” of the script is when it decides what to do with the index that is currently being looked at.
DECLARE @FragThreshold INT
DECLARE
@DoOfflineToo bit

---------------------------------------------------------------
-- VARIABLES TO CHANGE --
---------------------------------------------------------------

SELECT @FragThreshold = 10
SELECT  @DoOfflineToo = 0

---------------------------------------------------------------
---------------------------------------------------------------

DECLARE @objectid INT
DECLARE
@indexid INT
DECLARE
@partitioncount bigint
DECLARE @schemaname VARCHAR(130)
DECLARE @objectname VARCHAR(130)
DECLARE @indexname VARCHAR(130)
DECLARE @indextype INT
DECLARE
@partitionnum bigint
DECLARE @partitions bigint
DECLARE @frag INT
DECLARE
@command VARCHAR(4000)
DECLARE @max INT
DECLARE
@min INT
DECLARE
@DB_ID INT
DECLARE
@DB_NAME NVARCHAR(128)
DECLARE @IndexListCount INT
DECLARE
@Edition NVARCHAR(100)

DECLARE @LobCount INT
SELECT
@LobCount = 0
SELECT @DB_ID = DB_ID()
SELECT @DB_NAME = DB_NAME()
SELECT  @Edition = CONVERT(NVARCHAR(100), SERVERPROPERTY ('edition'))

CREATE TABLE #BenchmarkIndexList (ID INT IDENTITY(1,1),
  
DBName VARCHAR(100),
  
objectID INT,
  
indexID INT,
  
IndexType VARCHAR(30),
  
frag INT,
  
avg_frag_size_pages INT,
  
page_count INT,
  
partition_number INT)

CREATE CLUSTERED INDEX IX_CL_IndexList ON #BenchmarkIndexList(ID)

INSERT #BenchmarkIndexList (DBName, objectID, indexID, IndexType, frag,
                  
avg_frag_size_pages, page_count, partition_number)
SELECT DB_NAME(database_id) AS DBName, OBJECT_ID, index_id, index_type_desc AS 'IndexType',
      
avg_fragmentation_in_percent, avg_fragment_size_in_pages, page_count, partition_number
FROM   sys.dm_db_index_physical_stats (@DB_ID, NULL, NULL, NULL, 'LIMITED')
WHERE  page_count > 10 AND
      
index_id > 0 AND
      
avg_fragmentation_in_percent > @FragThreshold AND
      
index_type_desc NOT LIKE '%XML%'
      
SELECT @IndexListCount = COUNT(1) FROM #BenchmarkIndexList

SELECT @max = (SELECT MAX(ID) FROM #BenchmarkIndexList)
SELECT @min = 1
WHILE @min <= @max
BEGIN
   SELECT  
@objectid = objectID, @indexid = indexID, @partitionnum = partition_number, @frag = frag
  
FROM    #BenchmarkIndexList
  
WHERE   ID = @min

   SELECT  @objectname = o.name, @schemaname = s.name
  
FROM    sys.objects AS o JOIN
          
sys.schemas AS s ON s.schema_id = o.schema_id
  
WHERE   o.OBJECT_ID = @objectid

   SELECT  @indexname = name, @indextype = TYPE
   FROM    
sys.indexes
  
WHERE   OBJECT_ID = @objectid AND
          
index_id = @indexid;

   SELECT  @partitioncount = COUNT(1)
  
FROM    sys.partitions
  
WHERE   OBJECT_ID = @objectid AND
          
index_id = @indexid
      
  
SELECT  @LobCount = COUNT(1)
  
FROM    sys.index_columns A INNER JOIN
          
sys.columns B ON A.OBJECT_ID = B.OBJECT_ID AND A.Column_id = B.Column_ID INNER JOIN
          
sys.types C ON B.system_type_id = C.user_type_id
  
WHERE   A.OBJECT_ID = @objectID AND
          
A.index_id = @indexID AND
           (
C.name IN('xml','image','text','ntext') OR
           (
C.name IN('varchar','nvarchar','varbinary','nvarbinary') AND
          
B.max_length = -1))

   IF @frag < @FragThreshold
  
BEGIN
       SET
@command = 'ALTER INDEX ' + @indexname + ' ON ' + @schemaname + '.' + @objectname + ' REORGANIZE';
  
END

   IF @frag >= @FragThreshold AND @IndexType <> 1 AND @LobCount = 0 AND @Edition = 'Enterprise Edition'
  
BEGIN
       SET
@command = 'ALTER INDEX ' + @indexname + ' ON ' + @schemaname + '.' + @objectname +' REBUILD WITH(ONLINE = ON, SORT_IN_TEMPDB = ON)';
  
END

   IF @frag >= @FragThreshold AND @DoOfflineToo = 1 AND (@IndexType = 1 OR @LobCount <> 0 OR @Edition <> 'Enterprise Edition')
  
BEGIN
       SET
@command = 'ALTER INDEX ' + @indexname + ' ON ' + @schemaname + '.' + @objectname +' REBUILD WITH(ONLINE = OFF, SORT_IN_TEMPDB = ON)';
  
END
  
       IF
@frag >= @FragThreshold AND @DoOfflineToo = 0 AND (@IndexType = 1 OR @LobCount <> 0 OR @Edition <> 'Enterprise Edition')
  
BEGIN
       SET
@command = 'ALTER INDEX ' + @indexname + ' ON ' + @schemaname + '.' + @objectname + ' REORGANIZE';
  
END

   IF @partitioncount > 1
  
BEGIN
       SET
@command = @command + ' PARTITION =' + CAST(@partitionnum AS VARCHAR(10));
      
EXEC (@command)
  
END

   ELSE
   BEGIN
       EXEC (@command)
  
END
  
   SET
@min = @min+1

END

DROP TABLE #BenchmarkIndexList

Post to Twitter Post to Delicious Post to Digg Post to StumbleUpon

2 Comments

  1. Julia says:

    There are 2 spelling mistakes in this script:
    EEND
    @objeectname

    I’m using it now, hopefully it will work.

    Julia

  2. admin says:

    Thank you very much for pointing those out. They should be resolved now and please let me know how the script works out for you.

    Thanks again!

    Colin

Leave a Reply

Twitter links powered by Tweet This v1.6.1, a WordPress plugin for Twitter.