SQL Query to Get Details of Index Fragmentation in SharePoint


SQL Query to get details of Index Fragmentation in SharePoint
The dynamic management view "sys.dm_db_index_physical_stats" returns size and fragmentation information for the data and indexes of the specified table or view. For an index, one row is returned for each level of the B-tree in each partition. For a heap, one row is returned for the IN_ROW_DATA allocation unit of each partition. For Large OBject (LOB) data, one row is returned for the LOB_DATA allocation unit of each partition. If row-overflow data exists in the table, one row is returned for the ROW_OVERFLOW_DATA allocation unit in each partition. This does not return information about xVelocity memory optimized columnstore indexes.
The following query returns all the indexes and their fragmentation statistics in the current content database as shown in the figure below.
Run the following on the WSS_Content Database:

    OBJECT_NAME(A.[object_id]) as 'TableName',

    B.[name] as 'IndexName',

    A.[index_id],

    A.[page_count],

    A.[index_type_desc],

    A.[avg_fragmentation_in_percent],

    A.[fragment_count]

FROM

    sys.dm_db_index_physical_stats(db_id(),NULL,NULL,NULL,'LIMITED') A INNER JOIN

    sys.indexes B ON A.[object_id] = B.[object_id] and A.index_id = B.index_id 

Order by A.[avg_fragmentation_in_percent] desc
The result will be something like the figure below.

Index-Fragmentation-in-SharePoint-1.jpg

Figure: Index Fragmentation details in Content database.
General guidelines for interpreting the "avg_fragmentation_in_percent" value are as follows.

Index-Fragmentation-in-SharePoint-2.jpg

SQL Script to rebuild or reorganize indexes

The following script can be used to rebuild and reorganize indexes. In this example 30% fragmentation is an arbitrary decision point at which to switch between reorganizing and rebuilding. This can be changed as per your organization's decision.

--  <<DB_NAME>> to be replaced with the Content Database name

USE <<DB_NAME>>

GO

SET NOCOUNT ON;

DECLARE @objectid int;

DECLARE @indexid int;

DECLARE @partitioncount bigint;

DECLARE @schemaname nvarchar(130);

DECLARE @objectname nvarchar(130);

DECLARE @indexname nvarchar(130);

DECLARE @partitionnum bigint;

DECLARE @partitions bigint;

DECLARE @frag float;

DECLARE @command nvarchar(4000);

-- Conditionally select tables and indexes from the sys.dm_db_index_physical_stats function

-- and convert object and index IDs to names.

SELECT

    object_id AS objectid,

    index_id AS indexid,

    partition_number AS partitionnum,

    avg_fragmentation_in_percent AS frag

INTO #work_to_do

FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'LIMITED')

WHERE avg_fragmentation_in_percent > 10.0 AND index_id > 0;

-- Declare the cursor for the list of partitions to be processed.

DECLARE partitions CURSOR FOR SELECT * FROM #work_to_do;

-- Open the cursor.

OPEN partitions;

-- Loop through the partitions.

WHILE (1=1)

    BEGIN;

        FETCH NEXT

           FROM partitions

           INTO @objectid, @indexid, @partitionnum, @frag;

        IF @@FETCH_STATUS < 0 BREAK;

        SELECT @objectname = QUOTENAME(o.name), @schemaname = QUOTENAME(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 = QUOTENAME(name)

        FROM sys.indexes

        WHERE  object_id = @objectid AND index_id = @indexid;

        SELECT @partitioncount = count (*)

        FROM sys.partitions

        WHERE object_id = @objectid AND index_id = @indexid;

-- 30 is an arbitrary decision point at which to switch between reorganizing and rebuilding.

        IF @frag < 30.0

            SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE';

        IF @frag >= 30.0

            SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD';

        IF @partitioncount > 1

            SET @command = @command + N' PARTITION=' + CAST(@partitionnum AS nvarchar(10));

        EXEC (@command);

        PRINT N'Executed: ' + @command;

    END;

-- Close and deallocate the cursor.

CLOSE partitions;

DEALLOCATE partitions;

-- Drop the temporary table.

DROP TABLE #work_to_do;

GO