SQL Server 2019 创建和使用索引 --- 语法知识点及使用方法详解
一、索引的含义和特点
1. 含义
索引是数据库中一种特殊的查找表,用于加速对数据表中数据的检索。它类似于书籍的目录,通过建立关键字与数据物理位置的映射,提高查询效率。
2. 特点
- 加快查询速度:特别是 WHERE、JOIN、ORDER BY 等操作。
- 降低写入性能:INSERT、UPDATE、DELETE 操作需维护索引结构。
- 占用存储空间:索引本身需要磁盘空间。
- 自动维护:SQL Server 在数据变更时自动更新索引。
- 可选择性创建:并非所有列都需要索引。
二、索引的分类
1. 聚集索引(Clustered Index)
- 特点:决定表中数据的物理存储顺序;一个表只能有一个聚集索引。
- 适用:常用于主键或经常范围查询的列。
2. 非聚集索引(Nonclustered Index)
- 特点:独立于数据行的结构,包含索引键值和指向数据行的指针;一个表可建多个。
- 适用:用于频繁查询但不修改的列。
3. 唯一索引(Unique Index)
- 特点:确保索引列无重复值,可用于实施 UNIQUE 约束。
4. 包含列索引(Index with Included Columns)
- 特点:非键列包含在叶级别,用于覆盖查询,避免书签查找。
5. 筛选索引(Filtered Index)
- 特点:对表中部分行建立索引,提高查询性能并减少索引维护开销。
6. 全文索引(Full-Text Index)
- 特点:用于对文本数据进行关键字搜索,支持模糊匹配。
7. XML 索引(XML Index)
- 特点:用于加速 XML 数据类型的查询。
8. 空间索引(Spatial Index)
- 特点:用于 geography 和 geometry 数据类型的空间查询。
三、索引的设计原则
- 高频查询列优先建索引:WHERE、JOIN、ORDER BY、GROUP BY 中的列。
- 高选择性列优先:如身份证号、邮箱等重复值少的列。
- 避免过多索引:每增加一个索引,写操作性能下降。
- 组合索引注意顺序:最常用的列放在前面;范围查询列放最后。
- 定期维护索引:重建或重组以消除碎片。
- 避免对小表建索引:全表扫描可能更快。
- 监控索引使用情况:通过 DMV 查看是否被使用。
四、创建索引
1. 使用对象资源管理器创建索引(图形界面操作)
步骤:
- 展开数据库 → 表 → 目标表 → "索引"节点。
- 右键 → "新建索引" → 选择索引类型。
- 添加索引列,设置排序(升序/降序)。
- 可选:设置包含列、筛选条件、唯一性等。
- 点击"确定"完成。
适用场景:适合初学者或临时调整,生产环境建议使用脚本。
2. 使用 Transact-SQL 语句创建索引
语法结构:
sql
CREATE [UNIQUE] [CLUSTERED | NONCLUSTERED] INDEX index_name
ON table_name (column_name [ASC | DESC] [ ,...n ] )
[INCLUDE (column_name [ ,...n ] )]
[WHERE filter_predicate]
[WITH (
PAD_INDEX = { ON | OFF },
FILLFACTOR = fillfactor,
SORT_IN_TEMPDB = { ON | OFF },
IGNORE_DUP_KEY = { ON | OFF },
STATISTICS_NORECOMPUTE = { ON | OFF },
DROP_EXISTING = { ON | OFF },
ONLINE = { ON | OFF },
ALLOW_ROW_LOCKS = { ON | OFF },
ALLOW_PAGE_LOCKS = { ON | OFF },
MAXDOP = max_degree_of_parallelism
)]
[ON { partition_scheme_name | filegroup_name } ]
案例1:创建聚集索引
sql
-- 在 Employees 表的 EmployeeID 列上创建聚集索引
-- 通常主键默认是聚集索引,但也可手动指定
CREATE CLUSTERED INDEX IX_Employees_EmployeeID
ON dbo.Employees (EmployeeID ASC);
GO
注释:
CLUSTERED表示聚集索引。ASC表示升序(默认),也可用DESC降序。- 一个表只能有一个聚集索引。
案例2:创建非聚集索引
sql
-- 在 Employees 表的 LastName 列上创建非聚集索引,用于加速按姓氏查询
CREATE NONCLUSTERED INDEX IX_Employees_LastName
ON dbo.Employees (LastName ASC);
GO
注释:
NONCLUSTERED可省略(默认)。- 适用于高频查询但非主键的列。
案例3:创建唯一索引
sql
-- 在 Employees 表的 Email 列上创建唯一非聚集索引,防止重复邮箱
CREATE UNIQUE NONCLUSTERED INDEX IX_Employees_Email
ON dbo.Employees (Email ASC)
WHERE Email IS NOT NULL; -- 可选:允许NULL值(多个NULL不违反唯一性)
GO
注释:
UNIQUE确保列值唯一。WHERE Email IS NOT NULL是筛选索引,只对非空值建索引。
案例4:创建包含列的索引(覆盖索引)
sql
-- 查询常需要:LastName, FirstName, Department
-- 创建索引包含 LastName 作为键列,FirstName 和 Department 作为包含列
-- 避免回表查询(书签查找)
CREATE NONCLUSTERED INDEX IX_Employees_LastName_Includes
ON dbo.Employees (LastName ASC)
INCLUDE (FirstName, Department);
GO
注释:
INCLUDE子句将非键列包含在叶级别。- 适用于 SELECT 中的非 WHERE 列,提高覆盖查询效率。
案例5:创建复合索引(多列索引)
sql
-- 经常按 Department 和 HireDate 范围查询
-- Department 选择性高放前面,HireDate 范围查询放后面
CREATE NONCLUSTERED INDEX IX_Employees_Dept_HireDate
ON dbo.Employees (Department ASC, HireDate DESC);
GO
注释:
- 复合索引顺序很重要:高选择性、等值查询列在前,范围查询列在后。
- 查询 WHERE Department = 'IT' AND HireDate > '2020-01-01' 可高效使用此索引。
案例6:创建筛选索引
sql
-- 只对在职员工(Status = 'Active')建索引,减少索引大小和维护开销
CREATE NONCLUSTERED INDEX IX_Employees_Active_LastName
ON dbo.Employees (LastName)
WHERE Status = 'Active';
GO
注释:
WHERE子句定义筛选条件。- 适用于数据子集查询频繁的场景。
案例7:创建索引并指定填充因子(FillFactor)
sql
-- 设置填充因子为 80%,预留 20% 空间用于未来插入,减少页分裂
CREATE NONCLUSTERED INDEX IX_Employees_LastName_FillFactor
ON dbo.Employees (LastName)
WITH (FILLFACTOR = 80, ONLINE = ON); -- ONLINE=ON 允许在线操作(企业版)
GO
注释:
FILLFACTOR = 80表示每页填满80%,留20%空间。ONLINE = ON允许在创建索引时表仍可读写(仅企业版支持)。
五、管理和维护索引
1. 查看索引信息
方法1:使用系统视图 sys.indexes
sql
-- 查看指定表的所有索引信息
SELECT
i.name AS IndexName,
i.type_desc AS IndexType,
c.name AS ColumnName,
ic.is_included_column AS IsIncludedColumn,
i.fill_factor AS FillFactor,
i.is_unique AS IsUnique,
i.has_filter AS HasFilter,
i.filter_definition AS FilterDefinition
FROM sys.indexes i
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE i.object_id = OBJECT_ID('dbo.Employees')
ORDER BY i.name, ic.key_ordinal;
GO
方法2:使用存储过程 sp_helpindex
sql
-- 快速查看表索引结构
EXEC sp_helpindex 'dbo.Employees';
GO
方法3:查看索引使用统计(DMV)
sql
-- 查看索引被使用的次数,识别无用索引
SELECT
OBJECT_NAME(i.object_id) AS TableName,
i.name AS IndexName,
i.type_desc AS IndexType,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates,
s.last_user_seek,
s.last_user_scan
FROM sys.indexes i
LEFT JOIN sys.dm_db_index_usage_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID()
WHERE OBJECT_NAME(i.object_id) = 'Employees'
ORDER BY s.user_seeks + s.user_scans + s.user_lookups DESC;
GO
2. 重命名索引
sql
-- 将索引 IX_Employees_LastName 重命名为 IX_Emp_LastName
EXEC sp_rename
@objname = 'dbo.Employees.IX_Employees_LastName',
@newname = 'IX_Emp_LastName',
@objtype = 'INDEX';
GO
注释:
sp_rename用于重命名对象。- 格式:
'schema.table.index_name'- 重命名后需更新相关脚本或应用程序引用。
3. 删除索引
sql
-- 删除名为 IX_Emp_LastName 的索引
DROP INDEX IX_Emp_LastName ON dbo.Employees;
GO
注释:
- 删除索引会释放空间,但可能影响查询性能。
- 删除前建议检查索引使用情况。
六、综合性案例
综合案例1:为电商订单系统优化索引
sql
-- 场景:Orders 表,包含百万级数据
-- 常见查询:
-- 1. 按 CustomerID 查询订单
-- 2. 按 OrderDate 范围查询
-- 3. 按 Status 查询(如 'Shipped')
-- 4. 查询时需返回 OrderID, OrderDate, TotalAmount, CustomerID
-- 步骤1:创建复合索引满足多条件查询
CREATE NONCLUSTERED INDEX IX_Orders_CustomerID_OrderDate
ON dbo.Orders (CustomerID ASC, OrderDate DESC)
INCLUDE (TotalAmount, Status); -- 覆盖常用查询列
GO
-- 步骤2:为状态查询创建筛选索引(只对未发货订单)
CREATE NONCLUSTERED INDEX IX_Orders_Status_Pending
ON dbo.Orders (OrderDate DESC)
INCLUDE (CustomerID, TotalAmount)
WHERE Status = 'Pending';
GO
-- 步骤3:查看索引是否创建成功
EXEC sp_helpindex 'dbo.Orders';
GO
-- 步骤4:模拟查询,验证索引使用
SET STATISTICS IO ON;
SELECT OrderID, OrderDate, TotalAmount, CustomerID
FROM dbo.Orders
WHERE CustomerID = 1001 AND OrderDate >= '2025-01-01'
ORDER BY OrderDate DESC;
-- 应使用 IX_Orders_CustomerID_OrderDate,逻辑读取大幅减少
GO
SET STATISTICS IO OFF;
综合案例2:索引维护脚本(重建高碎片索引)
sql
-- 查找碎片率 > 30% 的索引,并重建
DECLARE @SchemaName NVARCHAR(128);
DECLARE @TableName NVARCHAR(128);
DECLARE @IndexName NVARCHAR(128);
DECLARE @SQL NVARCHAR(MAX);
DECLARE IndexCursor CURSOR FOR
SELECT
s.name AS SchemaName,
t.name AS TableName,
i.name AS IndexName
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ps
INNER JOIN sys.tables t ON ps.object_id = t.object_id
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
INNER JOIN sys.indexes i ON ps.object_id = i.object_id AND ps.index_id = i.index_id
WHERE ps.avg_fragmentation_in_percent > 30
AND i.name IS NOT NULL
AND ps.page_count > 1000; -- 忽略小索引
OPEN IndexCursor;
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName, @IndexName;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = N'ALTER INDEX ' + QUOTENAME(@IndexName) +
N' ON ' + QUOTENAME(@SchemaName) + '.' + QUOTENAME(@TableName) +
N' REBUILD WITH (ONLINE = ON, FILLFACTOR = 90);';
PRINT 'Rebuilding: ' + @SQL;
EXEC sp_executesql @SQL;
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName, @IndexName;
END
CLOSE IndexCursor;
DEALLOCATE IndexCursor;
GO
注释:
- 使用
sys.dm_db_index_physical_stats检测碎片。REBUILD重建索引,消除碎片。ONLINE = ON减少阻塞(企业版)。- 定期在维护窗口执行。
综合案例3:动态创建缺失索引建议(基于 DMV)
sql
-- 查询 SQL Server 建议的缺失索引
SELECT
migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
'CREATE NONCLUSTERED INDEX IX_' +
REPLACE(REPLACE(mid.statement, ']', ''), '[', '') + '_' +
REPLACE(REPLACE(ISNULL(mid.equality_columns,''), '], [', '_'), '[', '') + '_' +
REPLACE(REPLACE(ISNULL(mid.inequality_columns,''), '], [', '_'), '[', '') +
' ON ' + mid.statement + ' (' +
ISNULL(mid.equality_columns, '') +
CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END +
ISNULL(mid.inequality_columns, '') + ')' +
ISNULL(' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement,
migs.user_seeks,
migs.user_scans,
migs.last_user_seek,
migs.avg_user_impact
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) > 10
ORDER BY improvement_measure DESC;
GO
注释:
- 此脚本生成 SQL Server 建议的缺失索引语句。
- 需结合业务分析,不可盲目创建。
improvement_measure越高,潜在收益越大。
七、最佳实践总结
- 定期监控索引使用情况,删除无用索引。
- 避免过度索引,特别是写密集型表。
- 组合索引列顺序:等值查询在前,范围查询在后。
- 使用包含列减少书签查找。
- 大表使用筛选索引提高效率。
- 维护索引碎片:>30% 重建,10%~30% 重组。
- 测试索引效果 :使用
SET STATISTICS IO ON对比逻辑读取。
✅ 以上内容涵盖 SQL Server 2019 索引的核心语法、管理操作及实战案例,可直接用于学习、开发与调优。