| name | index-strategies |
| description | SQL Server index design and optimization strategies. Use this skill when: (1) User needs help designing indexes, (2) User asks about clustered vs nonclustered indexes, (3) User wants to optimize columnstore indexes, (4) User needs filtered or covering indexes, (5) User asks about index maintenance and fragmentation.
|
Index Strategies
Comprehensive guide to SQL Server index design and optimization.
Quick Reference
Index Types
| Type | Description | Best For |
|---|
| Clustered | Table data order | Primary access path, range scans |
| Nonclustered | Separate structure | Specific query patterns |
| Columnstore | Column-based storage | Analytics, aggregations |
| Filtered | Partial index | Well-known subsets |
| Covering | All columns needed | Avoiding key lookups |
Clustered Index Guidelines
Ideal Clustered Key:
- Narrow (small data type)
- Unique or mostly unique
- Ever-increasing (identity, sequential GUID)
- Static (rarely updated)
CREATE CLUSTERED INDEX CIX_Orders ON Orders(OrderID);
CREATE TABLE Orders (
OrderID UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY CLUSTERED
);
Nonclustered Index Design
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID
ON Orders(CustomerID);
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, TotalAmount, Status);
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
CREATE NONCLUSTERED INDEX IX_Orders_DateDesc
ON Orders(OrderDate DESC, OrderID DESC);
Index Selection Guide
By Query Pattern
| Pattern | Recommended Index |
|---|
WHERE Col = value | Nonclustered on Col |
WHERE Col = v1 AND Col2 = v2 | Nonclustered on (Col, Col2) |
WHERE Col = v ORDER BY Col2 | Nonclustered on (Col, Col2) |
WHERE Col BETWEEN x AND y | Col as leftmost key |
SELECT * WHERE Col = v | Clustered or covering NC |
| Large aggregations | Columnstore |
| Specific subset queries | Filtered index |
Column Order in Composite Keys
CREATE INDEX IX_Example ON Table(A, B, C);
WHERE A = 1
WHERE A = 1 AND B = 2
WHERE A = 1 AND B = 2 AND C = 3
WHERE A = 1 AND B > 5 ORDER BY B
WHERE B = 2
WHERE B = 2 AND C = 3
WHERE A = 1 AND C = 3
Columnstore Indexes
Clustered Columnstore
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales;
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales
ORDER (DateKey, ProductKey);
Nonclustered Columnstore
CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_Orders_Analysis
ON Orders(OrderDate, ProductID, Quantity, Amount)
WHERE Status = 'Completed';
Columnstore Best Practices
- Load batches >= 102,400 rows - Creates compressed segments
- Order data by filtered columns - Better segment elimination
- Use REORGANIZE, not REBUILD - More efficient maintenance
- Avoid frequent small updates - Causes deltastore fragmentation
- Partition by date - Enables partition elimination
ALTER INDEX CCI_FactSales ON FactSales REORGANIZE;
SELECT
object_name(object_id) AS TableName,
index_id,
avg_fragmentation_in_percent,
fragment_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED');
Filtered Indexes
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
CREATE UNIQUE INDEX IX_Users_Email
ON Users(Email)
WHERE Email IS NOT NULL;
Covering Indexes
CREATE INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, Amount, Status);
Index Maintenance
Fragmentation Guidelines
| Fragmentation % | Action |
|---|
| < 5% | None needed |
| 5-30% | REORGANIZE |
| > 30% | REBUILD |
ALTER INDEX IX_Orders_CustomerID ON Orders REORGANIZE;
ALTER INDEX IX_Orders_CustomerID ON Orders REBUILD;
ALTER INDEX IX_Orders_CustomerID ON Orders
REBUILD WITH (ONLINE = ON);
ALTER INDEX IX_Orders_CustomerID ON Orders
REBUILD WITH (ONLINE = ON, RESUMABLE = ON, MAX_DURATION = 60);
ALTER INDEX IX_Orders_CustomerID ON Orders RESUME;
Statistics Update
UPDATE STATISTICS Orders;
UPDATE STATISTICS Orders WITH FULLSCAN;
SELECT
OBJECT_NAME(object_id) AS TableName,
name AS StatsName,
STATS_DATE(object_id, stats_id) AS LastUpdated
FROM sys.stats
WHERE object_id = OBJECT_ID('Orders');
Performance Monitoring
Index Usage Stats
SELECT
OBJECT_NAME(i.object_id) AS TableName,
i.name AS IndexName,
ius.user_seeks,
ius.user_scans,
ius.user_lookups,
ius.user_updates
FROM sys.indexes i
LEFT JOIN sys.dm_db_index_usage_stats ius
ON i.object_id = ius.object_id
AND i.index_id = ius.index_id
WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
ORDER BY ius.user_seeks + ius.user_scans DESC;
Missing Index Recommendations
SELECT
migs.avg_user_impact AS ImpactPercent,
mid.statement AS TableName,
mid.equality_columns,
mid.inequality_columns,
mid.included_columns
FROM sys.dm_db_missing_index_groups mig
JOIN sys.dm_db_missing_index_group_stats migs
ON mig.index_group_handle = migs.group_handle
JOIN sys.dm_db_missing_index_details mid
ON mig.index_handle = mid.index_handle
ORDER BY migs.avg_user_impact DESC;