Chapter 21 · SQL Server Agent, Maintenance, Automation, Policy, and Operational Governance

Index/Statistics Maintenance Based on Evidence Instead of Blind Rebuild Schedules

Choose deliberately among AGs, replication, log shipping, CDC/ETL and application replication from the information and failover contract actually required.

Advanced190–240 minutesevidence-based maintenance labSQL Server 2025 CU7 · 17.0.4065.4Compatibility 170Query Store + index/stats DMVs · August 2026

Learning outcomes

Many SQL Server estates still run a script that rebuilds every index above a fixed fragmentation percentage every night. That script often creates more work than it removes: large log generation, extra tempdb usage for sorts/versioning, blocking or lock pressure, AG send/redo backlog, replication volume, plan recompilation and unnecessary SSD I/O. Index maintenance should start with a degraded workload hypothesis, not a threshold copied from an old blog post.

01

Use page density, fragmentation, page count, workload usage, and statistics modification evidence together.

02

Distinguish the effects of REORGANIZE, REBUILD, and UPDATE STATISTICS.

03

Explain why fragmentation alone does not justify maintenance and why page density can matter more.

04

Estimate operational side effects on log, tempdb, blocking, backups, AGs, and replication.

05

Build a no-op-by-default maintenance decision that records why an action was or was not taken.

Lab baseline. SQL Server 2025 (17.x), CU7 build 17.0.4065.4; database compatibility level 170 unless stated otherwise. SQL Server Agent is available in Standard/Standard Developer and Enterprise/Enterprise Developer but not Express. PowerShell scripting support and SSMS/sqlcmd remain available with Express, so every mandatory exercise has an Express-compatible manual or PowerShell path. Policy automation (scheduled/change evaluation) is also not an Express capability. SSMS 22.8.2 is the current checked SSMS release. Use the Microsoft SqlServer PowerShell module rather than legacy SQLPS; Azure Data Studio is retired. Labs are single-instance and non-production unless a topology is explicitly labeled optional.

1. Start with the query and the access pattern

Logical fragmentation describes leaf-page order; page density describes how full the pages are. Fragmentation mostly matters for workloads that perform large ordered scans and can benefit from efficient read-ahead. If a query performs point seeks, eliminating fragmentation might have no measurable benefit. Low page density can be more broadly expensive because more pages must be cached and read for the same rows, and the optimizer’s I/O cost model can change.

sql · build an evidence table and indexes
USE master;GOIF DB_ID(N'ServiceHubOpsLab') IS NULLBEGIN    CREATE DATABASE ServiceHubOpsLab;END;GOALTER DATABASE ServiceHubOpsLab SET RECOVERY SIMPLE;ALTER DATABASE ServiceHubOpsLab SET COMPATIBILITY_LEVEL = 170;GOUSE ServiceHubOpsLab;GOIF SCHEMA_ID(N'lab21') IS NULL EXEC(N'CREATE SCHEMA lab21 AUTHORIZATION dbo;');GOIF OBJECT_ID(N'lab21.RunAudit', N'U') IS NULLBEGIN  CREATE TABLE lab21.RunAudit  (    run_id bigint IDENTITY PRIMARY KEY,    run_name sysname NOT NULL,    started_at datetime2(0) NOT NULL DEFAULT SYSUTCDATETIME(),    finished_at datetime2(0) NULL,    outcome varchar(16) NOT NULL DEFAULT 'STARTED',    detail nvarchar(1000) NULL  );END;GOIF OBJECT_ID(N'lab21.WorkQueue', N'U') IS NULLBEGIN  CREATE TABLE lab21.WorkQueue  (    work_id bigint IDENTITY PRIMARY KEY,    status varchar(16) NOT NULL,    created_at datetime2(0) NOT NULL DEFAULT SYSUTCDATETIME(),    processed_at datetime2(0) NULL,    payload nvarchar(200) NULL  );  INSERT lab21.WorkQueue(status,payload)  SELECT TOP (5000)    CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 5 = 0 THEN 'READY' ELSE 'DONE' END,    CONCAT(N'work-',ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))  FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;END;GOUSE ServiceHubOpsLab;GOIF OBJECT_ID(N'lab21.OrderHistory',N'U') IS NULLBEGIN  CREATE TABLE lab21.OrderHistory  (    order_id int IDENTITY PRIMARY KEY,    customer_id int NOT NULL,    status varchar(16) NOT NULL,    order_date date NOT NULL,    amount decimal(12,2) NOT NULL,    padding char(100) NULL  );  INSERT lab21.OrderHistory(customer_id,status,order_date,amount,padding)  SELECT TOP (30000)    1 + ABS(CHECKSUM(NEWID())) % 4000,    CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 7=0 THEN 'OPEN' ELSE 'CLOSED' END,    DATEADD(day,-(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 730),CONVERT(date,SYSUTCDATETIME())),    10 + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 5000),    REPLICATE('x',100)  FROM sys.all_objects a CROSS JOIN sys.all_objects b;  CREATE INDEX IX_OrderHistory_StatusDate    ON lab21.OrderHistory(status,order_date)    INCLUDE(customer_id,amount);END;GO

The random inserts here are for a disposable diagnostic dataset, not a benchmark. Before maintenance, record the query shape and whether the index participates in a scan or seek. Query Store from Chapters 10–11 can provide before/after runtime evidence; index usage DMVs are transient since restart/failover and therefore need collection context.

sql · collect physical, usage, and statistics evidence
SELECT  i.name, ps.page_count, ps.avg_fragmentation_in_percent,  ps.avg_page_space_used_in_percent,  us.user_seeks, us.user_scans, us.user_updatesFROM sys.indexes AS iCROSS APPLY sys.dm_db_index_physical_stats  (DB_ID(),OBJECT_ID(N'lab21.OrderHistory'),i.index_id,NULL,'SAMPLED') AS psLEFT JOIN sys.dm_db_index_usage_stats AS us  ON us.database_id=DB_ID() AND us.object_id=i.object_id AND us.index_id=i.index_idWHERE i.object_id=OBJECT_ID(N'lab21.OrderHistory')ORDER BY i.index_id;GOSELECT s.name,sp.last_updated,sp.rows,sp.rows_sampled,sp.modification_counterFROM sys.stats AS sCROSS APPLY sys.dm_db_stats_properties(s.object_id,s.stats_id) AS spWHERE s.object_id=OBJECT_ID(N'lab21.OrderHistory');GO

A tiny 40-page index can report dramatic percentages but be cheaper to leave alone than to maintain. A large, scan-heavy index with poor density and correlated query regression deserves more attention. Never treat one DMV row as a maintenance command generator.

2. REORGANIZE, REBUILD, and statistics updates are different interventions

ALTER INDEX ... REORGANIZE is an online, incremental leaf-level operation that compacts pages toward the stored fill factor and reduces logical fragmentation. It does not update index statistics. ALTER INDEX ... REBUILD creates a new index structure and updates index statistics as part of the build; it can be much more resource-intensive. Online rebuild availability is edition/version sensitive. UPDATE STATISTICS can refresh the cardinality model without rewriting the entire index.

Microsoft’s current guidance explicitly warns that perceived “rebuild benefits” are often statistics-refresh benefits. Therefore if a query regresses because estimates are stale, updating the relevant statistics may achieve the goal with far less log, I/O and blocking than rebuilding the index.

sql · compare scoped maintenance actions
-- Use one intervention at a time and measure representative queries.UPDATE STATISTICS lab21.OrderHistory IX_OrderHistory_StatusDate WITH RESAMPLE;GO-- REORGANIZE is online and does not update statistics.ALTER INDEX IX_OrderHistory_StatusDate ON lab21.OrderHistory REORGANIZE;GO-- REBUILD is more invasive; ONLINE=ON is edition-sensitive.-- ALTER INDEX IX_OrderHistory_StatusDate ON lab21.OrderHistory--   REBUILD WITH (ONLINE=ON);GO
No fixed 5/30-percent rule. Those thresholds are historical heuristics, not SQL Server invariants. Choose an action only when size, access pattern, page density/fragmentation, statistics state and measured workload impact justify it.

3. Deliberately wrong: rebuild everything nightly

A nightly ALTER INDEX ALL ... REBUILD across every database can create a burst of transaction-log generation, extend log-backup duration, fill a log that was otherwise right-sized, consume tempdb for online/sort operations, block schema locks, and push large redo/send queues to availability-group replicas. It also creates needless writes on storage and can invalidate plans. If replication or CDC observes the same database, maintenance can interact with their log/retention pressure even though index rebuild itself is not “replicated as row changes” in the simple sense.

The repair is a decision procedure: identify queries/indexes where maintenance might matter; compare Query Store/runtime evidence; choose no-op, stats update, reorganize or rebuild; limit scope to the object/partition; record resource preconditions; verify after. “Do nothing” is a valid maintenance action.

sql · record an evidence-based decision instead of a blind action
DECLARE @idx int = INDEXPROPERTY(OBJECT_ID(N'lab21.OrderHistory'),N'IX_OrderHistory_StatusDate','IndexID');DECLARE @frag decimal(9,2), @density decimal(9,2), @pages bigint;SELECT TOP (1)  @frag=avg_fragmentation_in_percent,  @density=avg_page_space_used_in_percent,  @pages=page_countFROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID(N'lab21.OrderHistory'),@idx,NULL,'SAMPLED');INSERT lab21.RunAudit(run_name,finished_at,outcome,detail)VALUES(N'IndexAssessment',SYSUTCDATETIME(),'SUCCEEDED',       CONCAT(N'pages=',@pages,N'; frag=',@frag,N'; density=',@density,              N'; action=NO AUTOMATIC ACTION - correlate with workload evidence'));SELECT TOP (3) * FROM lab21.RunAudit ORDER BY run_id DESC;GO

This is intentionally conservative. It demonstrates that collection and decision are separate. A production framework can encode approved policy, but the policy should still have object exclusions, resource guards, partition awareness, time budgets, cancellation/rollback behavior, and a path to override based on measured regressions.

4. Statistics maintenance needs its own policy

Automatic statistics creation/update handles many workloads well, but not all. Large tables, skew, ascending keys, selective predicates and bursty modifications can need targeted updates. Use sys.dm_db_stats_properties and Query Store estimate/runtime evidence instead of “statistics older than seven days” alone: a statistic can be old and perfect because the data did not change, or recent and unrepresentative because the sample missed important skew.

Rebuilding every index merely to update statistics is especially wasteful. If statistics are the problem, fix statistics. If page density or fragmentation demonstrably hurts scan performance, fix the relevant index. Keep cause and intervention aligned.

5. Production judgment

Maintenance must fit the surrounding system. Before rebuilds or large stats jobs, inspect free log space, log-backup health, tempdb headroom, current blocking, AG send/redo queues, storage latency, replication/CDC health, and the business window. Record engine build, edition, database compatibility, recovery model, index/partition, command options and observed result. Resumable/online features can reduce operational risk where supported, but they do not remove resource cost.

The next lesson moves from object maintenance to estate governance: instead of changing objects on a fixed schedule, how do you define desired configuration, detect drift, and decide what a policy engine may safely enforce?

Check your understanding

  1. Why is fragmentation alone insufficient to justify an index rebuild?
  2. Which operation does not update index statistics: REORGANIZE or REBUILD?
  3. Why can a rebuild appear to fix a query even when fragmentation was not the cause?
  4. Name two operational costs of blind rebuilds.
  5. When is NO ACTION the correct maintenance decision?
Review the answers

1. Its performance impact depends on index size, scan/read-ahead behavior, storage, page density and actual query workload.

2. REORGANIZE does not update statistics; REBUILD updates the index statistics as part of building the index.

3. The rebuild refreshes index statistics, and the better cardinality estimates may be the actual source of improvement.

4. Examples: transaction-log growth, tempdb usage, blocking/schema locks, storage writes, plan churn, AG send/redo backlog, or longer backup windows.

5. When evidence does not show that index/statistics maintenance would materially improve the workload or when current resource/risk conditions make intervention unjustified.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.