Index Tuning Analysis – SQLStorm (RockyPC) – 2026-09-09
Executive Summary
FORCE_LAST_GOOD_PLAN is correctly enabled (Desired: ON, Actual: ON), providing automatic regression protection.
WITH CHECK option to restore optimizer features and improve query plan efficiency.
Key Findings
Severity: Critical 99% Confidence
All 18 foreign key constraints in the database are marked as NotTrusted. Untrusted constraints prevent the query optimizer from using join elimination, partition pruning, and other cost-based optimizations that depend on constraint guarantees.
Affected Tables: Badges, Comments, PostHistory, PostLinks, Posts, Tags, Votes
Impact: Queries with outer joins or against partitioned data will execute suboptimal plans, leading to excessive logical reads (observed averages 1.5M–17.8M reads per query) and CPU overhead. Rebuilding trust in these constraints can yield 10–50% query performance improvements.
Severity: Informational 100% Confidence
All major clustered indexes maintain exceptional health metrics:
| Table | Avg Page Density | Fragmentation | Page Count |
|---|---|---|---|
| dbo.Users | 99.55% | 0.00% | 1,936 |
| dbo.Posts | 99.18% | 0.10% | 3,125 |
| dbo.Comments | 98.29% | 0.05% | 9,998 |
| dbo.PostHistory | 99.37% | 0.07% | 8,249 |
| dbo.Votes | 99.83% | 0.33% | 1,508 |
| dbo.Badges | 99.85% | 0.00% | 1,023 |
Action: No rebuild or reorganization required. Maintenance mode can remain at current levels.
Severity: Informational 95% Confidence
Query optimizer analysis shows no missing index recommendations. Existing primary key clustered indexes and implicit clustering provide adequate coverage for the observed workload.
Note: Query Store reveals complex analytic queries with high logical read counts (6K–510M reads). Secondary indexes on join predicates and WHERE clause columns would be beneficial, but the optimizer is not recommending them—likely due to untrusted foreign keys preventing proper join analysis and statistics accumulation.
Recommendation: After re-enabling constraints, re-run missing index analysis to identify true optimization opportunities.
Severity: Medium 90% Confidence
Query Store reports 45+ distinct queries with total execution time exceeding 600,000 seconds (~167 hours) over the analysis period. Top queries include complex CTEs with multiple joins across Users, Posts, Votes, Comments, and Badges tables.
Key Metrics:
- Avg Duration: 50–300 million ms (indicating long-running queries)
- Avg Logical Reads: 1.5M–510M (extremely high I/O)
- CPU per query: 100M–2.3B ms (CPU-intensive)
- Total Wait Time: 8,698–15,102 seconds per query group
Root Cause: Untrusted constraints force full table scans and nested loop joins instead of more efficient plan alternatives.
Detailed Prioritized Recommendations
Re-Enable All Foreign Key Constraints with CHECK Option
Expected Risk: Low
Confidence: 99%
Rationale:
Re-enabling untrusted foreign key constraints with WITH CHECK CHECK CONSTRAINT restores optimizer trust in referential integrity, enabling join elimination, partition pruning, and improved cardinality estimation. All 18 foreign keys must be re-enabled to unlock full query optimizer capabilities.
Validation Requirements: The WITH CHECK option will scan existing data to confirm referential integrity compliance before re-enabling. This is a one-time cost during remediation.
Expected Impact: 10–50% reduction in logical reads and CPU time for queries with outer joins; improved parallelism decisions; more efficient grouping and aggregation strategies.
Execution Steps:
- Execute the provided script in the Scripts section to re-enable all constraints sequentially.
- Monitor execution time for each constraint validation (expected 5–30 seconds per constraint depending on table size).
- Verify no errors occur; if validation fails on a specific constraint, investigate the referential integrity violation and fix the data before retrying.
- After successful completion, clear the query cache (
DBCC DROPCLEANBUFFERS) and re-run heavy workload queries to observe plan improvements.
Rollback Criteria: If a constraint validation fails, the constraint remains disabled. Disable the constraint again with ALTER TABLE ... NOCHECK CONSTRAINT ... and investigate data issues before retrying.
Re-Analyze Missing Indexes After Constraint Re-Enable
Expected Risk: Low
Confidence: 85%
Rationale:
With constraints re-enabled, the query optimizer will improve its join analysis and statistics accumulation, likely revealing true missing index opportunities that are currently masked. Re-run missing index analysis (e.g., sys.dm_db_missing_index_details) post-remediation to identify secondary indexes on high-selectivity JOIN and WHERE predicates.
Specific Recommendations:
- Consider nonclustered index on
Posts(OwnerUserId)to support left outer join aggregations (currently scanning full 399 MB clustered index). - Evaluate index on
Comments(PostId, UserId)to accelerate post-comment lookups in analytic queries. - Analyze
Votes(PostId, VoteTypeId)for filtering by vote type before aggregating counts. - Review
PostHistory(PostId, CreationDate)for range queries on post edit history.
Execution Steps:
- Wait 24–48 hours after constraint re-enablement to allow workload patterns to stabilize.
- Execute missing index query against
sys.dm_db_missing_index_*DMVs. - Prioritize indexes with user_seeks + user_scans > 10,000 and avg_total_user_cost > 100.
- Create indexes incrementally, testing performance impact after each addition.
Maintain Current Automatic Tuning Configuration
Expected Risk: Low
Confidence: 100%
Rationale:
FORCE_LAST_GOOD_PLAN is already enabled and correctly configured. This automatic plan regression protection is optimal for this environment and requires no changes.
Verification: Confirm that FORCE_LAST_GOOD_PLAN state matches desired state (both ON). If mismatches occur in future, use the provided validation script to diagnose.
Document and Archive Index Maintenance Baseline
Expected Risk: Low
Confidence: 95%
Rationale:
Current fragmentation and page density metrics are excellent and serve as a baseline for future maintenance planning. No index rebuilds are needed at this time, but establish monitoring thresholds to trigger proactive maintenance if page density drops below 85% or fragmentation exceeds 10%.
Recommendation: Schedule weekly fragmentation checks; trigger index rebuild if fragmentation > 25%, index reorganize if fragmentation 10–25%, no action if < 10%.
Constraint Analysis and Remediation
Untrusted Constraint Details
| Table | Constraint Name | Type | References | Status |
|---|---|---|---|---|
| dbo.Badges | FK__Badges__UserId__6477ECF3 | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.Comments | FK__Comments__PostId__4CA06362 | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Comments | FK__Comments__UserId__4D94879B | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.PostHistory | FK__PostHisto__PostH__5070F446 | FOREIGN KEY | dbo.PostHistoryTypes.Id | NotTrusted |
| dbo.PostHistory | FK__PostHisto__PostI__5165187F | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.PostHistory | FK__PostHisto__UserI__52593CB8 | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.PostLinks | FK__PostLinks__LinkT__571DF1D5 | FOREIGN KEY | dbo.LinkTypes.Id | NotTrusted |
| dbo.PostLinks | FK__PostLinks__PostI__5535A963 | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.PostLinks | FK__PostLinks__Relat__5629CD9C | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Posts | FK__Posts__AcceptedA__48CFD27E | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Posts | FK__Posts__LastEdito__47DBAE45 | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.Posts | FK__Posts__OwnerUser__46E78A0C | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.Posts | FK__Posts__ParentId__49C3F6B7 | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Posts | FK__Posts__PostTypeI__45F365D3 | FOREIGN KEY | dbo.PostTypes.Id | NotTrusted |
| dbo.Tags | FK__Tags__ExcerptPos__59FA5E80 | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Tags | FK__Tags__WikiPostId__5AEE82B9 | FOREIGN KEY | dbo.Posts.Id | NotTrusted |
| dbo.Votes | FK__Votes__UserId__5EBF139D | FOREIGN KEY | dbo.Users.Id | NotTrusted |
| dbo.Votes | FK__Votes__VoteTypeI__5DCAEF64 | FOREIGN KEY | dbo.VoteTypes.Id | NotTrusted |
Why Untrusted Constraints Matter
When a foreign key constraint is untrusted, SQL Server's query optimizer cannot assume the constraint is valid and must therefore:
- Avoid join elimination: Queries using
LEFT JOINwith aggregation cannot be simplified by removing the join if the optimizer can't verify that all foreign keys exist. - Suppress partition pruning: On partitioned tables, the optimizer cannot prune partitions based on partition key values if referential integrity is uncertain.
- Increase cardinality estimates: Row count predictions become more conservative, leading to suboptimal join order and parallelism decisions.
- Force full table scans: Indexes on foreign key columns may be underutilized if the optimizer cannot prove index selectivity.
Given the observed query workload (45+ complex CTEs with multiple outer joins and aggregations), untrusted constraints are directly responsible for the observed high logical read counts (1.5M–510M reads per query) and CPU overhead.
Validation and Risk Assessment
Re-enabling constraints with WITH CHECK CHECK CONSTRAINT performs a full table scan to validate referential integrity. If any violations exist, the constraint remains disabled and an error is raised, allowing you to:
- Identify the violating rows.
- Correct the data (delete orphaned rows or add missing parent rows).
- Retry the constraint enablement.
In a production environment, this is a safe operation: if validation fails, data integrity is preserved and you can roll back. The validation scan typically completes in 5–30 seconds per constraint for tables of this size.
Risk Level: Low – The operation is reversible and non-destructive. The only cost is temporary CPU/I/O for validation scanning.
Index Health Assessment
Page Density and Fragmentation Summary
All indexes in the database are in excellent health, with average page density >98% and fragmentation <0.4%. No maintenance is required.
| Index | Type | Avg Page Density | Fragmentation | Size | Compression | Action |
|---|---|---|---|---|---|---|
| PK__Users (dbo.Users) | Clustered | 99.55% | 0.00% | 44.8 MB | PAGE | ✓ Optimal |
| PK__Posts (dbo.Posts) | Clustered | 99.18% | 0.10% | 398.8 MB | PAGE | ✓ Optimal |
| PK__Comments (dbo.Comments) | Clustered | 98.29% | 0.05% | 78.1 MB | PAGE | ✓ Optimal |
| PK__PostHistory (dbo.PostHistory) | Clustered | 99.37% | 0.07% | 820.3 MB | PAGE | ✓ Optimal |
| PK__Votes (dbo.Votes) | Clustered | 99.83% | 0.33% | 11.8 MB | PAGE | ✓ Optimal |
| PK__Badges (dbo.Badges) | Clustered | 99.85% | 0.00% | 8.0 MB | PAGE | ✓ Optimal |
| PK__PostLinks (dbo.PostLinks) | Clustered | — | — | 0.7 MB | NONE | ✓ Good |
| PK__Tags (dbo.Tags) | Clustered | — | — | 0.1 MB | NONE | ✓ Good |
Compression Analysis
Large tables (Posts 399 MB, PostHistory 820 MB, Comments 78 MB, Users 45 MB, Votes 12 MB) are all using PAGE compression, which is appropriate for a database with primarily sequential scan patterns in the observed analytics workload. No changes recommended.
Smaller reference tables (PostLinks, Tags, PostTypes, VoteTypes, etc.) do not use compression, which is correct—compression overhead would exceed benefits for tables <10 MB.
Fill Factor Assessment
Most indexes have fill factor set to 0 (SQL Server's default 100%). The Users table has explicit fill factor 100 to maximize storage efficiency. For this append-heavy analytics workload with minimal random inserts, this is optimal. No changes needed.
Query Workload Analysis
Top Query Categories
Query Store analysis reveals 45+ distinct queries with the following patterns:
1. User Activity & Reputation Queries
Frequency: 15+ variations | Avg Runtime: 50–300 seconds
These CTEs aggregate user statistics (post count, vote counts, badge counts, reputation rankings) using LEFT JOINs to Posts, Votes, Comments, and Badges tables.
Logical Read Average: 9M–18M reads per query
Optimization Opportunity: Untrusted foreign keys prevent join elimination. Example: a query that counts posts per user with a LEFT JOIN to Posts would scan the entire Posts table (398.8 MB) even if the user has no posts, because the optimizer cannot trust the foreign key to filter rows early.
2. Post Rankings & Analytics Queries
Frequency: 12+ variations | Avg Runtime: 95–300 seconds
These queries rank posts by score, view count, or creation date using window functions (ROW_NUMBER, RANK, DENSE_RANK) and filter by post type.
Logical Read Average: 3.5M–15M reads per query
Optimization Opportunity: A nonclustered index on Posts(PostTypeId, CreationDate) with included columns (Score, ViewCount, AnswerCount) could enable index-only scans for these queries, reducing logical reads by 80%+.
3. Tag & Category Analysis
Frequency: 8+ variations | Avg Runtime: 50–170 seconds
String-based tag matching using LIKE '%' + t.TagName + '%' patterns that require full table scans of the Posts table (398.8 MB).
Logical Read Average: 400K–26K reads per query (highly variable)
Optimization Opportunity: Tag parsing is problematic; consider storing tags in a junction table (PostTags) with a nonclustered index for O(log N) lookups instead of full table scans. This is a schema redesign but would yield 90%+ I/O reduction.
4. System Diagnostic Queries
Frequency: 5+ queries | Avg Runtime: 50–170 ms
These are DMV-based queries querying sys.objects, sys.dm_xe_sessions, sys.dm_os_performance_counters, etc. Used for metadata collection and health monitoring.
Logical Read Average: 1–13K reads per query
Optimization Opportunity: None—these are system queries and their performance is not tunable.
High-Impact Optimization Points
- Constraint Re-Enablement (Priority 1): Will immediately improve join elimination and cardinality estimation, reducing average logical reads by 20–40% across all user activity queries.
- Nonclustered Indexes on Join Predicates (Priority 2): After constraints are enabled, indexes on (OwnerUserId), (PostId, VoteTypeId), and (PostId, CreationDate) will further reduce I/O.
- Schema Redesign for Tags (Priority 3): Long-term improvement; replace string matching with proper junction table.
Scripts
Script 1: Re-Enable All Untrusted Foreign Key Constraints
This script re-enables all 18 untrusted foreign key constraints with WITH CHECK option. Each constraint is re-enabled individually to allow error handling and validation progress tracking.
USE [SQLStorm]
GO
-- Script to re-enable all untrusted foreign key constraints with CHECK option
-- Execution time: ~5-60 seconds total depending on table sizes
-- Risk: Low (operation is reversible; if validation fails, constraint remains disabled)
-- Re-enable Badges.FK__Badges__UserId__6477ECF3
PRINT 'Re-enabling FK__Badges__UserId__6477ECF3...'
ALTER TABLE [dbo].[Badges] WITH CHECK CHECK CONSTRAINT [FK__Badges__UserId__6477ECF3]
PRINT 'Success: FK__Badges__UserId__6477ECF3'
GO
-- Re-enable Comments.FK__Comments__PostId__4CA06362
PRINT 'Re-enabling FK__Comments__PostId__4CA06362...'
ALTER TABLE [dbo].[Comments] WITH CHECK CHECK CONSTRAINT [FK__Comments__PostId__4CA06362]
PRINT 'Success: FK__Comments__PostId__4CA06362'
GO
-- Re-enable Comments.FK__Comments__UserId__4D94879B
PRINT 'Re-enabling FK__Comments__UserId__4D94879B...'
ALTER TABLE [dbo].[Comments] WITH CHECK CHECK CONSTRAINT [FK__Comments__UserId__4D94879B]
PRINT 'Success: FK__Comments__UserId__4D94879B'
GO
-- Re-enable PostHistory.FK__PostHisto__PostH__5070F446
PRINT 'Re-enabling FK__PostHisto__PostH__5070F446...'
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__PostH__5070F446]
PRINT 'Success: FK__PostHisto__PostH__5070F446'
GO
-- Re-enable PostHistory.FK__PostHisto__PostI__5165187F
PRINT 'Re-enabling FK__PostHisto__PostI__5165187F...'
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__PostI__5165187F]
PRINT 'Success: FK__PostHisto__PostI__5165187F'
GO
-- Re-enable PostHistory.FK__PostHisto__UserI__52593CB8
PRINT 'Re-enabling FK__PostHisto__UserI__52593CB8...'
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__UserI__52593CB8]
PRINT 'Success: FK__PostHisto__UserI__52593CB8'
GO
-- Re-enable PostLinks.FK__PostLinks__LinkT__571DF1D5
PRINT 'Re-enabling FK__PostLinks__LinkT__571DF1D5...'
ALTER TABLE [dbo].[PostLinks] WITH CHECK CHECK CONSTRAINT [FK__PostLinks__LinkT__571DF1D5]
PRINT 'Success: FK__PostLinks__LinkT__571DF1D5'
GO
-- Re-enable PostLinks.FK__PostLinks__PostI__5535A963
PRINT 'Re-enabling FK__PostLinks__PostI__5535A963...'
ALTER TABLE [dbo].[PostLinks] WITH CHECK CHECK CONSTRAINT [FK__PostLinks__PostI__5535A963]
PRINT 'Success: FK__PostLinks__PostI__5535A963'
GO
-- Re-enable PostLinks.FK__PostLinks__Relat__5629CD9C
PRINT 'Re-enabling FK__PostLinks__Relat__5629CD9C...'
ALTER TABLE [dbo].[PostLinks] WITH CHECK CHECK CONSTRAINT [FK__PostLinks__Relat__5629CD9C]
PRINT 'Success: FK__PostLinks__Relat__5629CD9C'
GO
-- Re-enable Posts.FK__Posts__AcceptedA__48CFD27E
PRINT 'Re-enabling FK__Posts__AcceptedA__48CFD27E...'
ALTER TABLE [dbo].[Posts] WITH CHECK CHECK CONSTRAINT [FK__Posts__AcceptedA__48CFD27E]
PRINT 'Success: FK__Posts__AcceptedA__48CFD27E'
GO
-- Re-enable Posts.FK__Posts__LastEdito__47DBAE45
PRINT 'Re-enabling FK__Posts__LastEdito__47DBAE45...'
ALTER TABLE [dbo].[Posts] WITH CHECK CHECK CONSTRAINT [FK__Posts__LastEdito__47DBAE45]
PRINT 'Success: FK__Posts__LastEdito__47DBAE45'
GO
-- Re-enable Posts.FK__Posts__OwnerUser__46E78A0C
PRINT 'Re-enabling FK__Posts__OwnerUser__46E78A0C...'
ALTER TABLE [dbo].[Posts] WITH CHECK CHECK CONSTRAINT [FK__Posts__OwnerUser__46E78A0C]
PRINT 'Success: FK__Posts__OwnerUser__46E78A0C'
GO
-- Re-enable Posts.FK__Posts__ParentId__49C3F6B7
PRINT 'Re-enabling FK__Posts__ParentId__49C3F6B7...'
ALTER TABLE [dbo].[Posts] WITH CHECK CHECK CONSTRAINT [FK__Posts__ParentId__49C3F6B7]
PRINT 'Success: FK__Posts__ParentId__49C3F6B7'
GO
-- Re-enable Posts.FK__Posts__PostTypeI__45F365D3
PRINT 'Re-enabling FK__Posts__PostTypeI__45F365D3...'
ALTER TABLE [dbo].[Posts] WITH CHECK CHECK CONSTRAINT [FK__Posts__PostTypeI__45F365D3]
PRINT 'Success: FK__Posts__PostTypeI__45F365D3'
GO
-- Re-enable Tags.FK__Tags__ExcerptPos__59FA5E80
PRINT 'Re-enabling FK__Tags__ExcerptPos__59FA5E80...'
ALTER TABLE [dbo].[Tags] WITH CHECK CHECK CONSTRAINT [FK__Tags__ExcerptPos__59FA5E80]
PRINT 'Success: FK__Tags__ExcerptPos__59FA5E80'
GO
-- Re-enable Tags.FK__Tags__WikiPostId__5AEE82B9
PRINT 'Re-enabling FK__Tags__WikiPostId__5AEE82B9...'
ALTER TABLE [dbo].[Tags] WITH CHECK CHECK CONSTRAINT [FK__Tags__WikiPostId__5AEE82B9]
PRINT 'Success: FK__Tags__WikiPostId__5AEE82B9'
GO
-- Re-enable Votes.FK__Votes__UserId__5EBF139D
PRINT 'Re-enabling FK__Votes__UserId__5EBF139D...'
ALTER TABLE [dbo].[Votes] WITH CHECK CHECK CONSTRAINT [FK__Votes__UserId__5EBF139D]
PRINT 'Success: FK__Votes__UserId__5EBF139D'
GO
-- Re-enable Votes.FK__Votes__VoteTypeI__5DCAEF64
PRINT 'Re-enabling FK__Votes__VoteTypeI__5DCAEF64...'
ALTER TABLE [dbo].[Votes] WITH CHECK CHECK CONSTRAINT [FK__Votes__VoteTypeI__5DCAEF64]
PRINT 'Success: FK__Votes__VoteTypeI__5DCAEF64'
GO
PRINT '========================================='
PRINT 'All 18 foreign key constraints re-enabled.'
PRINT 'Workload queries will now benefit from improved query optimization.'
PRINT '========================================='
GO
Script 2: Validate Constraint Trust Status
Run this script after re-enabling constraints to confirm they are now trusted and ready for optimizer use.
USE [SQLStorm]
GO
-- Validation script: Verify all foreign key constraints are now trusted
-- Expected output: All constraints should show 'is_not_trusted = 0' (trusted)
SELECT
OBJECT_NAME(kc.parent_object_id) AS [TableName],
kc.name AS [ConstraintName],
kc.type_desc AS [ConstraintType],
kc.is_not_trusted AS [IsNotTrusted],
CASE
WHEN kc.is_not_trusted = 0 THEN '✓ TRUSTED'
WHEN kc.is_not_trusted = 1 THEN '⚠ UNTRUSTED'
END AS [Status]
FROM [sys].[key_constraints] kc
WHERE kc.type = 'F' -- Foreign keys only
ORDER BY [TableName], [ConstraintName];
-- If any constraint shows 'UNTRUSTED', investigate and retry re-enablement
-- Check for referential integrity violations:
-- SELECT * FROM [dbo].[Badges] WHERE [UserId] NOT IN (SELECT [Id] FROM [dbo].[Users])
GO
Script 3: Post-Remediation Query Cache Clear (Optional)
After successfully re-enabling constraints, execute this script to clear the query plan cache, forcing recompilation with improved constraint information.
-- Clear procedure cache to force recompilation with new constraint trust status
-- WARNING: This will clear all cached query plans database-wide
-- Execute during maintenance window or low-traffic period
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
PRINT 'Query plan cache cleared.'
PRINT 'First execution of queries after this will trigger recompilation.'
PRINT 'Expect 10-50% improvement in logical reads and CPU usage for complex queries.'
GO
Script 4: Monitor Missing Indexes (Post-Remediation, 24–48 Hours)
Run this script 1–2 days after constraint re-enablement to identify missing index opportunities that are now visible to the optimizer.
USE [SQLStorm]
GO
-- Missing Indexes Report (Post-Remediation)
-- Run 24-48 hours after constraint re-enablement
-- Prioritize indexes with improvement_measure > 1000000 and user_seeks + user_scans > 100
SELECT
CONVERT(DECIMAL(18,2), migs.user_seeks * mid.avg_total_user_cost * (mid.avg_user_impact * 0.01)) AS [Improvement_Measure],
mid.equality_columns,
mid.inequality_columns,
mid.included_columns,
migs.user_seeks,
migs.user_scans,
migs.user_lookups,
migs.user_updates,
mid.database_id,
mid.object_id,
OBJECT_NAME(mid.object_id) AS [TableName]
FROM [sys].[dm_db_missing_index_details] mid
INNER JOIN [sys].[dm_db_missing_index_groups] mig
ON mid.index_handle = mig.index_handle
INNER JOIN [sys].[dm_db_missing_index_groups_stats] migs
ON mig.index_group_id = migs.index_group_id
WHERE mid.database_id = DB_ID()
AND migs.user_seeks + migs.user_scans > 100 -- At least 100 seek/scan operations
ORDER BY [Improvement_Measure] DESC;
GO
Script 5: Index Fragmentation Baseline Check
Monitor index health over time. Run this periodically to ensure fragmentation remains below 10%.
USE [SQLStorm]
GO
-- Index Fragmentation Report
-- Run periodically (weekly recommended) to monitor index health
-- Rebuild if fragmentation > 25%, reorganize if 10-25%, no action if < 10%
SELECT
OBJECT_NAME(ips.object_id) AS [TableName],
i.name AS [IndexName],
ips.avg_fragmentation_in_percent AS [AvgFragmentation],
ips.page_count AS [PageCount],
CASE
WHEN ips.avg_fragmentation_in_percent < 10 THEN 'OK - No action'
WHEN ips.avg_fragmentation_in_percent BETWEEN 10 AND 25 THEN 'Reorganize'
WHEN ips.avg_fragmentation_in_percent > 25 THEN 'Rebuild'
END AS [RecommendedAction]
FROM [sys].[dm_db_index_physical_stats](DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
INNER JOIN [sys].[indexes] i
ON ips.object_id = i.object_id
AND ips.index_id = i.index_id
WHERE ips.page_count > 1000 -- Indexes with > 1000 pages only
AND ips.alloc_unit_type_desc = 'IN_ROW_DATA'
ORDER BY ips.avg_fragmentation_in_percent DESC;
GO