Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save EitanBlumin/4fb686ffa07c6f4fd96595f01ec2f1dd to your computer and use it in GitHub Desktop.
Save EitanBlumin/4fb686ffa07c6f4fd96595f01ec2f1dd to your computer and use it in GitHub Desktop.
Check Index Fragmentation Based on Page Fullness and Fill Factor. Inspired by: https://www.erikdarlingdata.com/2019/10/because-your-index-maintenance-script-is-measuring-the-wrong-thing/
/**************************************************************************
Check Index Fragmentation based on Page Fullness and Fill Factor
***************************************************************************
Author: Eitan Blumin | https://www.eitanblumin.com
Version History:
2020-10-20 Added @MaxDOP parameter, and better comments & indentation
2020-01-07 First version
Description:
This script was inspired by Erik Darling's blog post here:
https://www.erikdarlingdata.com/2019/10/because-your-index-maintenance-script-is-measuring-the-wrong-thing/
!!! THIS SCRIPT MUST BE RUN IN THE CONTEXT OF THE DATABASE TO CHECK !!!
-----------------
!!! WARNING !!!
-----------------
This script uses "SAMPLED" mode for checking fragmentation,
which can potentially cause significant IO stress on a
large production server.
Use at your own risk!
**************************************************************************/
DECLARE
-- Parameters to limit which tables/indexes to check:
@MinPageCount INT = 1000
,@MinUserUpdates INT = 1000
-- Parameters to control when to change the FILLFACTOR:
,@MinFragmentationToReduceFillFactor100 INT = 50
,@MaxFragmentationToSetFillFactor100 INT = 20
-- Parameters to control when to recommend REBUILD commands:
,@MaxSpaceUsedForFillFactor100 INT = 90
,@MaxSpaceUsedForFillFactorLessThan100 INT = 75
-- Parameters to control settings of remediation (REBUILD) commands:
,@OnlineRebuild BIT = 0
,@SortInTempDB BIT = 0
,@MaxDOP TINYINT = NULL -- change to a value to limit DOP (e.g. MAXDOP=1 to disable parallelism)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT, ARITHABORT, XACT_ABORT ON;
IF @OnlineRebuild = 1 AND ISNULL(CONVERT(int, SERVERPROPERTY('EngineEdition')),0) NOT IN (3,5,8)
BEGIN
RAISERROR(N'Online Rebuild is not supported in this SQL Server edition.',16,1);
GOTO Quit;
END
DECLARE @CommandTemplate NVARCHAR(MAX)
SET @CommandTemplate = N'RAISERROR(N''{DATABASE}.{TABLE} - {INDEX}'',0,1) WITH NOWAIT;
ALTER INDEX {INDEX} ON {TABLE}
REBUILD WITH(SORT_IN_TEMPDB='
+ CASE WHEN @SortInTempDB = 1 THEN N'ON' ELSE N'OFF' END
+ N', ONLINE=' + CASE WHEN @OnlineRebuild = 1 THEN N'ON' ELSE N'OFF' END
+ CASE WHEN @MaxDOP IS NOT NULL THEN N', MAXDOP=' + CONVERT(nvarchar,@MaxDOP) ELSE N'' END
+ N'{FILLFACTOR});
GO'
SELECT
DatabaseName = DB_NAME()
, SchemaName = OBJECT_SCHEMA_NAME(t.object_id)
, TableName = t.name
, IndexName = ix.name
, Remediation =
REPLACE(REPLACE(REPLACE(REPLACE(@CommandTemplate
, N'{DATABASE}', QUOTENAME(DB_NAME()))
, N'{TABLE}', QUOTENAME(OBJECT_SCHEMA_NAME(t.object_id)) + N'.' + QUOTENAME(t.name))
, N'{INDEX}', QUOTENAME(ix.name))
, N'{FILLFACTOR}',
CASE
WHEN ix.fill_factor = 0 AND ps.avg_fragmentation_in_percent >= @MinFragmentationToReduceFillFactor100
THEN N', FILLFACTOR=90'
WHEN ix.fill_factor > 0 AND ps.avg_fragmentation_in_percent <= @MaxFragmentationToSetFillFactor100
THEN N', FILLFACTOR=100'
ELSE N''
END)
, ix.fill_factor
, RowsCount = (SELECT SUM(rows) FROM sys.partitions AS p WHERE p.object_id = t.object_id AND p.index_id = ix.index_id)
, us.user_updates
, us.last_user_update
, ps.avg_fragmentation_in_percent
, ps.avg_page_space_used_in_percent
, ps.record_count
, ps.page_count
, ps.compressed_page_count
, t.object_id
, ix.index_id
, ps.partition_number
FROM
sys.dm_db_index_usage_stats AS us
INNER JOIN
sys.tables AS t ON us.object_id = t.object_id
INNER JOIN
sys.indexes AS ix ON ix.object_id = t.object_id AND ix.index_id = us.index_id
CROSS APPLY
sys.dm_db_index_physical_stats(DB_ID(), t.object_id, ix.index_id, NULL, 'SAMPLED') AS ps
WHERE
us.database_id = DB_ID()
AND ps.alloc_unit_type_desc = 'IN_ROW_DATA'
AND t.is_ms_shipped = 0
AND us.user_updates >= @MinUserUpdates
AND ps.page_count >= @MinPageCount
AND
(
(
ix.fill_factor <> 0
AND ps.avg_page_space_used_in_percent <= @MaxSpaceUsedForFillFactorLessThan100
)
OR
(
ix.fill_factor = 0
AND ps.avg_page_space_used_in_percent <= @MaxSpaceUsedForFillFactor100
)
)
OPTION(MAXDOP 1);
Quit:
@EitanBlumin
Copy link
Author

Relevant post here by Mike Byrd with an interesting algorithm implementing an "incremental" change in fill factor based on fragmentation and extended events monitoring page splits:
https://www.sqlservercentral.com/articles/a-self-tuning-fill-factor-technique-for-sql-server-part-1

It should be possible to implement something similar based on page fullness instead, as an alternative to the simple 100-90 fill factor algorithm that I'm currently using.

@EitanBlumin
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment