IndexOptimize: add @MinIndexUsageDays to skip unused non-clustered indexes when DMV history is sufficient - #1015
Open
forward-thinkers-lab wants to merge 2 commits into
Conversation
added 2 commits
May 20, 2026 09:18
Adds a single user-facing parameter that skips apparently-unused non-clustered indexes, but only when sys.dm_db_index_usage_stats has accumulated enough history to be trusted (>= @MinIndexUsageDays). Trust is derived internally from MAX(sqlserver_start_time, database create_date, earliest DMV activity); the user cannot override it. When the DMV is too young, the script logs a clear diagnostic and falls back to fragmentation-only behavior. Backward compatible: default NULL preserves existing behavior.
The trust-check block was inserted before @CurrentAvailabilityGroup and @CurrentAvailabilityGroupRole are populated, so the guard condition evaluated NULL IS NOT NULL = FALSE and the block ran on AG secondaries — exactly the case it was meant to skip. Move the block to immediately before the @ExecuteAsUser check, after the AG and mirroring role discovery and their RAISERROR messages, so the guard sees real values. No change to the block's contents.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a single optional parameter,
@MinIndexUsageDays, that letsdbo.IndexOptimizeskip non-clustered indexes with no recorded reads insys.dm_db_index_usage_stats— but only when the DMV has been collecting data long enough on the current instance for the "no reads" signal to be meaningful.Default is
NULL(feature off). Existing behavior is preserved bit-for-bit.New parameter
@MinIndexUsageDaysintNULLWhen set:
user_seeks + user_scans + user_lookups = 0are deselected for this run only if the DMV for that database is judged old enough.Why
sys.dm_db_index_usage_statsis volatile. Its rows are reset or invalidated by:database_id)Reading the DMV without accounting for how long it has been accumulating produces false negatives — an index can look unused simply because the DMV is too young. Skipping (or, in other tools, dropping) on that basis is unsafe.
This change derives a per-database lower bound on DMV age and gates the unused-index logic on it.
How DMV age is estimated
SQL Server does not expose a "last reset" time for the DMV, so the script computes a conservative lower bound per database as the MAX of three signals:
sys.dm_os_sys_info.sqlserver_start_time— instance uptime.sys.databases.create_date— covers attach/restore/new DB.MIN(last_user_seek, last_user_scan, last_user_lookup, last_user_update)for the database — earliest observed activity in the DMV.The DMV is considered trusted when
DATEDIFF(HOUR, estimate, SYSDATETIME()) >= @MinIndexUsageDays * 24.How each reset scenario is covered:
sqlserver_start_timeis freshcreate_datecreate_dateMIN(last_user_*)advances to the clear timeThe trust threshold is derived from
@MinIndexUsageDaysand is intentionally not exposed as a separate parameter, so it can never be set looser than the usage rule it protects.Contexts where the trust check is skipped
ONLINE.rdsadmin.In these cases the procedure behaves as today (no unused-index skipping for that database).
Backward compatibility
NULLpreserves existing behavior exactly.Testing
All tests run on dedicated test instances; results verified via procedure output and
dbo.CommandLog.EXEC dbo.IndexOptimize @Databases = 'USER_DATABASES'. Output andCommandLogidentical to the prior version.@MinIndexUsageDays = 1. Non-clustered indexes with zero reads correctly deselected.@MinIndexUsageDays = 30. Unused-index filter bypassed; fragmentation maintenance still runs; output explains DMV age vs. required age.@MinIndexUsageDays = -1raises an error via the existing@Errorsmechanism.IndexID > 1predicate on the deselection update; verified on a clustered index with no recent reads.