1. 首页 > SQLServer教程 > 正文

SQLServer教程FG107-SQLServer索引高级管理与优化实战

内容简介

本文章主要介绍SQLServer索引的高级管理与优化技术,包括索引的基础概念、类型与结构、设计原则、维护策略、性能影响分析等。风哥教程参考SQLServer官方文档Indexes、Performance Tuning等相关内容,将官方理论知识转化为生产实用技能,帮助DBA和开发人员更好地设计、管理和优化索引,提高查询性能。

目录大纲

Part01-基础概念与理论知识

1.1 SQLServer索引基础概念

索引是SQLServer中用于提高查询性能的数据结构,它通过创建指向表中数据的指针来加速数据的检索。索引的主要作用是减少数据库引擎需要扫描的数据量,从而提高查询速度。然而,索引也会增加数据修改的开销,因此需要在查询性能和修改性能之间找到平衡。

1.2 SQLServer索引类型与结构

SQLServer支持多种类型的索引:

  • 聚集索引:根据索引键值对表数据进行物理排序,一个表只能有一个聚集索引
  • 非聚集索引:不改变表数据的物理排序,一个表可以有多个非聚集索引
  • 唯一索引:确保索引列的值唯一
  • 复合索引:基于多个列创建的索引
  • 覆盖索引:包含查询所需的所有列的索引
  • 过滤索引:基于筛选条件创建的索引,只包含满足条件的行
  • 空间索引:用于空间数据类型的索引
  • 全文索引:用于全文搜索的索引

索引的结构主要包括:

  • B树结构:大多数SQLServer索引使用B树结构,包括聚集索引和非聚集索引
  • 叶节点:对于聚集索引,叶节点包含表数据;对于非聚集索引,叶节点包含指向表数据的指针
  • 非叶节点:包含索引键值和指向子节点的指针,用于快速定位叶节点

1.3 SQLServer索引设计原则

索引设计应遵循以下原则:

  • 选择合适的索引键:选择经常用于查询条件、排序和分组的列作为索引键
  • 考虑列的选择性:选择选择性高的列(即值唯一或接近唯一的列)作为索引键
  • 避免过度索引:过多的索引会增加数据修改的开销,影响性能
  • 使用复合索引:对于经常一起使用的多个列,创建复合索引
  • 注意索引键的顺序:在复合索引中,将选择性高的列放在前面
  • 考虑覆盖索引:对于频繁查询的列,创建覆盖索引以避免回表操作
  • 定期维护索引:定期重建或重新组织索引,以保持索引的效率

Part02-生产环境规划与建议

2.1 索引设计环境规划

在生产环境中设计索引需要考虑以下因素:

  • 数据分布:了解表中数据的分布情况,选择合适的索引键
  • 查询模式:分析常见的查询模式,为频繁执行的查询创建合适的索引
  • 数据修改频率:对于频繁修改的表,需要平衡索引的数量和类型
  • 存储空间:索引会占用存储空间,需要合理规划
  • 硬件资源:考虑服务器的内存、CPU和磁盘性能,选择合适的索引策略

2.2 索引策略建议

索引策略应考虑以下因素:

  • 聚集索引选择:选择唯一、稳定、经常用于范围查询的列作为聚集索引,如主键
  • 非聚集索引选择:为经常用于查询条件、排序和分组的列创建非聚集索引
  • 复合索引设计:对于多列查询,创建复合索引,并注意列的顺序
  • 过滤索引使用:对于有固定筛选条件的查询,使用过滤索引减少索引大小
  • 覆盖索引使用:对于频繁查询的列组合,创建覆盖索引以提高性能

2.3 索引维护与管理建议

索引的维护与管理应注意:

  • 定期重建或重新组织:定期重建或重新组织碎片化的索引
  • 监控索引使用情况:监控索引的使用情况,识别未使用的索引
  • 更新统计信息:定期更新索引的统计信息,确保查询优化器生成准确的执行计划
  • 删除未使用的索引:删除未使用的索引,减少存储空间和维护开销
  • 分区索引:对于大型表,考虑使用分区索引提高性能和可维护性

Part03-生产环境项目实施方案

3.1 索引设计与创建

# 创建聚集索引
CREATE CLUSTERED INDEX PK_fgedu_users ON dbo.fgedu_users(user_id);
GO

# 创建非聚集索引
CREATE NONCLUSTERED INDEX IX_fgedu_users_email ON dbo.fgedu_users(email);
GO

# 创建复合索引
CREATE NONCLUSTERED INDEX IX_fgedu_orders_customer_date ON dbo.fgedu_orders(customer_id, order_date DESC);
GO

# 创建唯一索引
CREATE UNIQUE NONCLUSTERED INDEX UQ_fgedu_users_username ON dbo.fgedu_users(username);
GO

# 创建过滤索引
CREATE NONCLUSTERED INDEX IX_fgedu_orders_status_pending ON dbo.fgedu_orders(order_id)
WHERE status = ‘Pending’;
GO

# 创建覆盖索引
CREATE NONCLUSTERED INDEX IX_fgedu_orders_customer_amount ON dbo.fgedu_orders(customer_id, total_amount)
INCLUDE (order_date, status);
GO

3.2 索引维护与优化

# 查看索引碎片情况
SELECT
t.name AS table_name,
i.name AS index_name,
ips.index_type_desc,
ips.avg_fragmentation_in_percent,
ips.fragment_count,
ips.avg_fragment_size_in_pages
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.dm_db_index_physical_stats(
DB_ID(‘fgedudb’),
NULL,
NULL,
NULL,
‘DETAILED’
) ips ON t.object_id = ips.object_id AND i.index_id = ips.index_id
WHERE t.name LIKE ‘fgedu%’
AND i.name IS NOT NULL
ORDER BY ips.avg_fragmentation_in_percent DESC;
GO

# 重新组织索引(碎片率10%-30%)
ALTER INDEX IX_fgedu_users_email ON dbo.fgedu_users REORGANIZE;
GO

# 重建索引(碎片率>30%)
ALTER INDEX IX_fgedu_orders_customer_date ON dbo.fgedu_orders REBUILD;
GO

# 重建表上的所有索引
ALTER INDEX ALL ON dbo.fgedu_users REBUILD;
GO

# 更新索引统计信息
UPDATE STATISTICS dbo.fgedu_users;
GO

# 删除未使用的索引
DROP INDEX IX_fgedu_users_unused ON dbo.fgedu_users;
GO

3.3 索引监控与分析

# 查看索引使用情况
SELECT
t.name AS table_name,
i.name AS index_name,
us.user_seeks,
us.user_scans,
us.user_lookups,
us.user_updates,
us.last_user_seek,
us.last_user_scan,
us.last_user_lookup,
us.last_user_update
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.dm_db_index_usage_stats us ON t.object_id = us.object_id AND i.index_id = us.index_id
WHERE t.name LIKE ‘fgedu%’
AND us.database_id = DB_ID(‘fgedudb’)
ORDER BY (us.user_seeks + us.user_scans + us.user_lookups) DESC;
GO

# 查看缺失的索引
SELECT
migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
‘CREATE INDEX [IX_’ + OBJECT_NAME(mid.object_id) + ‘_’ +
REPLACE(REPLACE(REPLACE(ISNULL(mid.equality_columns, ”), ‘, ‘, ‘_’), ‘[‘, ”), ‘]’, ”) +
CASE
WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ‘_’
ELSE ”
END +
REPLACE(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
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
WHERE migs.database_id = DB_ID(‘fgedudb’)
ORDER BY improvement_measure DESC;
GO

# 创建索引监控表
CREATE TABLE dbo.fgedu_index_monitor (
monitor_id INT PRIMARY KEY IDENTITY,
table_name NVARCHAR(128) NOT NULL,
index_name NVARCHAR(128) NOT NULL,
user_seeks INT NOT NULL,
user_scans INT NOT NULL,
user_lookups INT NOT NULL,
user_updates INT NOT NULL,
avg_fragmentation_in_percent FLOAT NOT NULL,
monitor_time DATETIME NOT NULL DEFAULT GETDATE()
);
GO

# 创建索引监控存储过程
CREATE PROCEDURE dbo.usp_monitor_indexes
AS
BEGIN
SET NOCOUNT ON;

INSERT INTO dbo.fgedu_index_monitor (
table_name,
index_name,
user_seeks,
user_scans,
user_lookups,
user_updates,
avg_fragmentation_in_percent
)
SELECT
t.name AS table_name,
i.name AS index_name,
ISNULL(us.user_seeks, 0) AS user_seeks,
ISNULL(us.user_scans, 0) AS user_scans,
ISNULL(us.user_lookups, 0) AS user_lookups,
ISNULL(us.user_updates, 0) AS user_updates,
ISNULL(ips.avg_fragmentation_in_percent, 0) AS avg_fragmentation_in_percent
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
LEFT JOIN sys.dm_db_index_usage_stats us ON t.object_id = us.object_id AND i.index_id = us.index_id AND us.database_id = DB_ID(‘fgedudb’)
LEFT JOIN sys.dm_db_index_physical_stats(
DB_ID(‘fgedudb’),
t.object_id,
i.index_id,
NULL,
‘LIMITED’
) ips ON t.object_id = ips.object_id AND i.index_id = ips.index_id
WHERE t.name LIKE ‘fgedu%’
AND i.name IS NOT NULL;
END;
GO

# 执行索引监控
EXEC dbo.usp_monitor_indexes;
GO

# 查看监控结果
SELECT * FROM dbo.fgedu_index_monitor ORDER BY monitor_time DESC;
GO

Part04-生产案例与实战讲解

4.1 索引设计实战案例

# 创建测试表
CREATE TABLE dbo.fgedu_sales (
sales_id INT PRIMARY KEY IDENTITY,
product_id INT NOT NULL,
customer_id INT NOT NULL,
sale_date DATE NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
region NVARCHAR(50) NOT NULL,
salesperson NVARCHAR(100) NOT NULL
);
GO

# 插入测试数据
DECLARE @i INT = 1;
WHILE @i <= 10000 BEGIN INSERT INTO dbo.fgedu_sales ( product_id, customer_id, sale_date, quantity, unit_price, total_amount, region, salesperson ) VALUES (@i % 100, @i % 500, DATEADD(day, @i % 365, '2023-01-01'), @i % 10 + 1, 100.00 + @i, (100.00 + @i) * (@i % 10 + 1), CASE WHEN @i % 4 = 0 THEN 'North' WHEN @i % 4 = 1 THEN 'South' WHEN @i % 4 = 2 THEN 'East' ELSE 'West' END, 'Salesperson ' + CAST(@i % 10 + 1 AS NVARCHAR(10))); SET @i = @i + 1; END; GO # 分析查询模式 -- 频繁查询1:按region和sale_date查询 SELECT * FROM dbo.fgedu_sales WHERE region = 'North' AND sale_date BETWEEN '2023-01-01' AND '2023-01-31'; GO -- 频繁查询2:按customer_id查询并按sale_date排序 SELECT * FROM dbo.fgedu_sales WHERE customer_id = 100 ORDER BY sale_date DESC; GO -- 频繁查询3:按salesperson和total_amount查询 SELECT * FROM dbo.fgedu_sales WHERE salesperson = 'Salesperson 1' AND total_amount > 1000;
GO

# 设计索引
— 为查询1创建复合索引
CREATE NONCLUSTERED INDEX IX_fgedu_sales_region_sale_date ON dbo.fgedu_sales(region, sale_date);
GO

— 为查询2创建复合索引
CREATE NONCLUSTERED INDEX IX_fgedu_sales_customer_date ON dbo.fgedu_sales(customer_id, sale_date DESC);
GO

— 为查询3创建复合索引
CREATE NONCLUSTERED INDEX IX_fgedu_sales_salesperson_amount ON dbo.fgedu_sales(salesperson, total_amount);
GO

# 验证索引效果
— 查看执行计划
SET SHOWPLAN_TEXT ON;
GO

SELECT * FROM dbo.fgedu_sales WHERE region = ‘North’ AND sale_date BETWEEN ‘2023-01-01’ AND ‘2023-01-31’;
GO

SELECT * FROM dbo.fgedu_sales WHERE customer_id = 100 ORDER BY sale_date DESC;
GO

SELECT * FROM dbo.fgedu_sales WHERE salesperson = ‘Salesperson 1’ AND total_amount > 1000;
GO

SET SHOWPLAN_TEXT OFF;
GO

4.2 索引优化实战案例

# 查看索引碎片情况
SELECT
t.name AS table_name,
i.name AS index_name,
ips.avg_fragmentation_in_percent
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.dm_db_index_physical_stats(
DB_ID(‘fgedudb’),
t.object_id,
i.index_id,
NULL,
‘LIMITED’
) ips ON t.object_id = ips.object_id AND i.index_id = ips.index_id
WHERE t.name = ‘fgedu_sales’
AND i.name IS NOT NULL
ORDER BY ips.avg_fragmentation_in_percent DESC;
GO

# 结果
/*
table_name index_name avg_fragmentation_in_percent
———– ———————————– —————————-
fgedu_sales IX_fgedu_sales_region_sale_date 45.0
fgedu_sales IX_fgedu_sales_customer_date 38.0
fgedu_sales IX_fgedu_sales_salesperson_amount 32.0
fgedu_sales PK__fgedu_sa__3214EC07A1234567 5.0
*/

# 重建碎片化严重的索引
ALTER INDEX IX_fgedu_sales_region_sale_date ON dbo.fgedu_sales REBUILD;
GO

ALTER INDEX IX_fgedu_sales_customer_date ON dbo.fgedu_sales REBUILD;
GO

ALTER INDEX IX_fgedu_sales_salesperson_amount ON dbo.fgedu_sales REBUILD;
GO

# 重新组织碎片化较轻的索引
ALTER INDEX PK__fgedu_sa__3214EC07A1234567 ON dbo.fgedu_sales REORGANIZE;
GO

# 再次查看索引碎片情况
SELECT
t.name AS table_name,
i.name AS index_name,
ips.avg_fragmentation_in_percent
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.dm_db_index_physical_stats(
DB_ID(‘fgedudb’),
t.object_id,
i.index_id,
NULL,
‘LIMITED’
) ips ON t.object_id = ips.object_id AND i.index_id = ips.index_id
WHERE t.name = ‘fgedu_sales’
AND i.name IS NOT NULL
ORDER BY ips.avg_fragmentation_in_percent DESC;
GO

# 结果
/*
table_name index_name avg_fragmentation_in_percent
———– ———————————– —————————-
fgedu_sales PK__fgedu_sa__3214EC07A1234567 0.5
fgedu_sales IX_fgedu_sales_region_sale_date 0.3
fgedu_sales IX_fgedu_sales_customer_date 0.2
fgedu_sales IX_fgedu_sales_salesperson_amount 0.1
*/

4.3 索引问题排查案例

# 查看未使用的索引
SELECT
t.name AS table_name,
i.name AS index_name,
us.user_seeks,
us.user_scans,
us.user_lookups,
us.user_updates,
us.last_user_seek,
us.last_user_scan,
us.last_user_lookup
FROM sys.tables t
JOIN sys.indexes i ON t.object_id = i.object_id
LEFT JOIN sys.dm_db_index_usage_stats us ON t.object_id = us.object_id AND i.index_id = us.index_id AND us.database_id = DB_ID(‘fgedudb’)
WHERE t.name LIKE ‘fgedu%’
AND i.name IS NOT NULL
AND (us.user_seeks + us.user_scans + us.user_lookups) = 0
ORDER BY t.name, i.name;
GO

# 结果
/*
table_name index_name user_seeks user_scans user_lookups user_updates last_user_seek last_user_scan last_user_lookup
———– ———————– ———– ———– ————- ————- ————— ————— —————-
fgedu_users IX_fgedu_users_unused 0 0 0 100 NULL NULL NULL
*/

# 删除未使用的索引
DROP INDEX IX_fgedu_users_unused ON dbo.fgedu_users;
GO

# 查看缺失的索引
SELECT
migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
‘CREATE INDEX [IX_’ + OBJECT_NAME(mid.object_id) + ‘_’ +
REPLACE(REPLACE(REPLACE(ISNULL(mid.equality_columns, ”), ‘, ‘, ‘_’), ‘[‘, ”), ‘]’, ”) +
CASE
WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ‘_’
ELSE ”
END +
REPLACE(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
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
WHERE migs.database_id = DB_ID(‘fgedudb’)
ORDER BY improvement_measure DESC;
GO

# 结果
/*
improvement_measure create_index_statement
——————– —————————————————————
1250.5 CREATE INDEX [IX_fgedu_sales_product_id] ON [dbo].[fgedu_sales] ([product_id]) INCLUDE ([sale_date], [quantity], [total_amount])
*/

# 创建缺失的索引
CREATE INDEX [IX_fgedu_sales_product_id] ON [dbo].[fgedu_sales] ([product_id]) INCLUDE ([sale_date], [quantity], [total_amount]);
GO

Part05-风哥经验总结与分享

5.1 索引设计最佳实践

  • 分析查询模式:根据实际的查询模式设计索引,优先为频繁执行的查询创建索引
  • 选择合适的索引键:选择选择性高、稳定、经常用于查询条件的列作为索引键
  • 合理使用复合索引:对于多列查询,创建复合索引,并注意列的顺序
  • 使用覆盖索引:对于频繁查询的列组合,创建覆盖索引以避免回表操作
  • 考虑过滤索引:对于有固定筛选条件的查询,使用过滤索引减少索引大小
  • 避免过度索引:过多的索引会增加数据修改的开销,影响性能
  • 定期维护索引:定期重建或重新组织碎片化的索引,保持索引的效率

5.2 索引常见问题与解决方案

  • 索引碎片:索引碎片化导致查询性能下降。解决方案:定期重建或重新组织索引。
  • 未使用的索引:未使用的索引会增加存储空间和维护开销。解决方案:识别并删除未使用的索引。
  • 缺失的索引:缺少必要的索引导致查询性能下降。解决方案:根据缺失索引建议创建合适的索引。
  • 索引过度使用:过多的索引会增加数据修改的开销。解决方案:评估索引的必要性,删除不必要的索引。
  • 索引键选择不当:选择了选择性低的列作为索引键,导致索引效率低下。解决方案:重新评估索引键的选择。
  • 复合索引顺序不当:复合索引中列的顺序不当,导致索引无法被有效使用。解决方案:重新设计复合索引的列顺序。

5.3 索引监控与维护建议

  • 建立监控机制:定期监控索引的使用情况、碎片情况和性能影响
  • 自动化维护:使用作业定期重建或重新组织碎片化的索引
  • 性能影响评估:评估索引对查询性能和修改性能的影响,找到平衡点
  • 文档化管理:记录索引的设计、创建和维护历史,便于问题排查
  • 知识共享:建立索引设计和管理的最佳实践文档,共享经验和技巧
  • 持续优化:根据业务需求和查询模式的变化,持续优化索引设计

更多视频教程www.fgedu.net.cn

学习交流加群风哥微信: itpux-com

学习交流加群风哥QQ113257174

风哥提示:索引是SQLServer性能优化的重要工具,合理的索引设计和维护对于提高查询性能至关重要。

更多学习教程公众号风哥教程itpux_com

from SQLServer视频:www.itpux.com

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息