index-strategies
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIndex Strategies
索引策略
Comprehensive guide to SQL Server index design and optimization.
SQL Server索引设计与优化综合指南。
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 | 表数据按该索引顺序存储 | 主要访问路径、范围扫描 |
| Nonclustered | 独立于表的索引结构 | 特定查询模式 |
| Columnstore | 基于列的存储结构 | 分析查询、聚合计算 |
| Filtered | 仅包含表中部分数据的索引 | 针对已知数据子集的查询 |
| Covering | 包含查询所需全部列的索引 | 避免键查找 |
Clustered Index Guidelines
Clustered Index 设计准则
Ideal Clustered Key:
- Narrow (small data type)
- Unique or mostly unique
- Ever-increasing (identity, sequential GUID)
- Static (rarely updated)
sql
-- Good: Identity column
CREATE CLUSTERED INDEX CIX_Orders ON Orders(OrderID);
-- Good: Sequential GUID
CREATE TABLE Orders (
OrderID UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY CLUSTERED
);
-- Avoid: Wide composite keys, frequently updated columns, GUIDs (NEWID)理想的Clustered Key:
- 窄键(数据类型占用空间小)
- 唯一或基本唯一
- 持续递增(比如identity列、顺序GUID)
- 静态(极少更新)
sql
-- Good: Identity column
CREATE CLUSTERED INDEX CIX_Orders ON Orders(OrderID);
-- Good: Sequential GUID
CREATE TABLE Orders (
OrderID UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY CLUSTERED
);
-- Avoid: Wide composite keys, frequently updated columns, GUIDs (NEWID)Nonclustered Index Design
Nonclustered Index 设计
sql
-- Basic index
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID
ON Orders(CustomerID);
-- Covering index (avoids key lookup)
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, TotalAmount, Status);
-- Filtered index (partial)
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
-- Descending order
CREATE NONCLUSTERED INDEX IX_Orders_DateDesc
ON Orders(OrderDate DESC, OrderID DESC);sql
-- Basic index
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID
ON Orders(CustomerID);
-- Covering index (avoids key lookup)
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, TotalAmount, Status);
-- Filtered index (partial)
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
-- Descending order
CREATE NONCLUSTERED INDEX IX_Orders_DateDesc
ON Orders(OrderDate DESC, OrderID DESC);Index Selection Guide
索引选择指南
By Query Pattern
按查询模式选择
| Pattern | Recommended Index |
|---|---|
| Nonclustered on Col |
| Nonclustered on (Col, Col2) |
| Nonclustered on (Col, Col2) |
| Col as leftmost key |
| Clustered or covering NC |
| Large aggregations | Columnstore |
| Specific subset queries | Filtered index |
| 查询模式 | 推荐索引 |
|---|---|
| Nonclustered Index(基于Col列) |
| Nonclustered Index(基于Col、Col2列) |
| Nonclustered Index(基于Col、Col2列) |
| 将Col作为索引最左键 |
| Clustered Index 或包含所有列的Nonclustered Index |
| 大型聚合查询 | Columnstore Index |
| 针对特定子集的查询 | Filtered Index |
Column Order in Composite Keys
复合索引中的列顺序
sql
-- Order matters! Left-to-right matching
CREATE INDEX IX_Example ON Table(A, B, C);
-- These queries CAN use the index:
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
-- These queries CANNOT use index seek:
WHERE B = 2 -- A not specified
WHERE B = 2 AND C = 3 -- A not specified
WHERE A = 1 AND C = 3 -- B skipped (partial match only)sql
-- 顺序很重要!从左到右匹配查询条件
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 -- 未指定A列
WHERE B = 2 AND C = 3 -- 未指定A列
WHERE A = 1 AND C = 3 -- 跳过了B列(仅部分匹配)Columnstore Indexes
Columnstore Indexes
Clustered Columnstore
Clustered Columnstore
sql
-- Best for data warehousing
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales;
-- Ordered columnstore (SQL 2022+)
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales
ORDER (DateKey, ProductKey);sql
-- 最适用于数据仓库场景
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales;
-- 有序列存储索引(SQL 2022+)
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON FactSales
ORDER (DateKey, ProductKey);Nonclustered Columnstore
Nonclustered Columnstore
sql
-- Hybrid OLTP/OLAP
CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_Orders_Analysis
ON Orders(OrderDate, ProductID, Quantity, Amount)
WHERE Status = 'Completed';sql
-- 混合OLTP/OLAP场景适用
CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_Orders_Analysis
ON Orders(OrderDate, ProductID, Quantity, Amount)
WHERE Status = 'Completed';Columnstore Best Practices
Columnstore Index 最佳实践
- 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
sql
-- Maintenance
ALTER INDEX CCI_FactSales ON FactSales REORGANIZE;
-- Check fragmentation
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');- 批量加载≥102400行 - 可创建压缩段
- 按筛选列排序数据 - 提升段消除效率
- 使用REORGANIZE而非REBUILD - 维护更高效
- 避免频繁的小批量更新 - 会导致增量存储碎片
- 按日期分区 - 支持分区消除
sql
-- 维护操作
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
Filtered Indexes
sql
-- Index active orders only
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
-- Index non-NULL values
CREATE UNIQUE INDEX IX_Users_Email
ON Users(Email)
WHERE Email IS NOT NULL;
-- Constraints:
-- - Cannot use variable in filter
-- - Query WHERE must match or be subset of filter WHERE
-- - May cause parameter sniffing issuessql
-- 仅为活跃订单创建索引
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON Orders(CustomerID, OrderDate)
WHERE Status = 'Active';
-- 仅为非NULL值创建唯一索引
CREATE UNIQUE INDEX IX_Users_Email
ON Users(Email)
WHERE Email IS NOT NULL;
-- 约束条件:
-- - 筛选条件中不能使用变量
-- - 查询的WHERE子句必须与筛选条件匹配或为其子集
-- - 可能会导致参数嗅探问题Covering Indexes
Covering Indexes
sql
-- Eliminate key lookups
-- Original: Index on CustomerID, query selects OrderDate, Amount
-- Execution plan shows Key Lookup
-- Solution: Covering index
CREATE INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, Amount, Status);
-- INCLUDE columns:
-- - Not in key (not sorted)
-- - Stored at leaf level only
-- - Don't contribute to 900-byte key limit
-- - Perfect for frequently selected columnssql
-- 消除键查找
-- 原场景:基于CustomerID列的索引,查询需要返回OrderDate、Amount列
-- 执行计划中会显示Key Lookup(键查找)
-- 解决方案:使用Covering Index
CREATE INDEX IX_Orders_CustomerID_Cover
ON Orders(CustomerID)
INCLUDE (OrderDate, Amount, Status);
-- INCLUDE列特性:
-- - 不属于索引键(不参与排序)
-- - 仅存储在索引叶节点
-- - 不受900字节的索引键长度限制
-- - 非常适合频繁被查询的列Index Maintenance
索引维护
Fragmentation Guidelines
碎片处理准则
| Fragmentation % | Action |
|---|---|
| < 5% | None needed |
| 5-30% | REORGANIZE |
| > 30% | REBUILD |
sql
-- Reorganize (online, minimal locking)
ALTER INDEX IX_Orders_CustomerID ON Orders REORGANIZE;
-- Rebuild (offline by default, more thorough)
ALTER INDEX IX_Orders_CustomerID ON Orders REBUILD;
-- Online rebuild (Enterprise Edition)
ALTER INDEX IX_Orders_CustomerID ON Orders
REBUILD WITH (ONLINE = ON);
-- Resumable rebuild (SQL 2017+)
ALTER INDEX IX_Orders_CustomerID ON Orders
REBUILD WITH (ONLINE = ON, RESUMABLE = ON, MAX_DURATION = 60);
-- Resume interrupted rebuild
ALTER INDEX IX_Orders_CustomerID ON Orders RESUME;| 碎片占比 | 操作建议 |
|---|---|
| < 5% | 无需处理 |
| 5-30% | 执行REORGANIZE |
| > 30% | 执行REBUILD |
sql
-- 重新组织(在线操作,锁占用少)
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);
-- 恢复中断的重建操作(SQL 2017+)
ALTER INDEX IX_Orders_CustomerID ON Orders RESUME;Statistics Update
统计信息更新
sql
-- Update after index changes
UPDATE STATISTICS Orders;
-- Full scan for accurate stats
UPDATE STATISTICS Orders WITH FULLSCAN;
-- Check last update
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');sql
-- 索引变更后更新统计信息
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 OBJECTPROPERTY(object_id, 'IsUserTable') = 1;Performance Monitoring
性能监控
Index Usage Stats
索引使用统计
sql
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;sql
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
缺失索引建议
sql
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;sql
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;