SQLServer教程FG088-SQLServer索引优化实战
本文档风哥主要介绍SQLServer数据库索引优化相关知识,包括SQLServer数据库索引规划、SQLServer数据库索引实施方案、SQLServer数据库索引配置、SQLServer数据库索引验证等内容,风哥教程参考SQLServer官方文档索引优化内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。
Part01-基础概念与理论知识
1.1 SQLServer数据库索引概念
SQLServer索引是一种数据库对象,用于提高查询性能。索引通过创建数据的快速查找结构,减少了查询时需要扫描的数据量,从而加快了数据检索速度。索引类似于书籍的目录,允许数据库引擎快速定位数据,而不需要扫描整个表。索引是SQLServer性能优化的重要手段,合理的索引设计可以显著提高查询性能。更多视频教程www.fgedu.net.cn
- 提高查询性能
- 加速数据检索
- 减少I/O操作
- 支持高效的排序和分组
- 提高系统整体性能
1.2 SQLServer数据库索引类型
1. 聚集索引 (Clustered Index)
– 按照索引键的顺序存储数据
– 每个表只能有一个聚集索引
– 通常基于主键创建
– 适合范围查询和排序操作
2. 非聚集索引 (Nonclustered Index)
– 单独存储索引结构,与数据分开
– 每个表可以有多个非聚集索引
– 包含索引键和指向数据的指针
– 适合点查询和小范围查询
3. 唯一索引 (Unique Index)
– 确保索引列的值唯一
– 可以是聚集或非聚集
– 自动创建唯一约束
– 防止重复数据
4. 覆盖索引 (Covering Index)
– 包含查询所需的所有列
– 避免书签查找
– 提高查询性能
– 减少I/O操作
5. 过滤索引 (Filtered Index)
– 基于WHERE子句创建的索引
– 只包含满足条件的数据
– 减少索引大小
– 提高查询性能
6. 全文索引 (Full-Text Index)
– 用于全文搜索
– 支持复杂的文本搜索
– 提高文本查询性能
– 适合大量文本数据
7. 空间索引 (Spatial Index)
– 用于空间数据类型
– 提高空间查询性能
– 支持地理和几何数据
– 适合位置相关查询
8. 列存储索引 (Columnstore Index)
– 按列存储数据
– 高压缩率
– 适合分析查询
– 提高数据仓库性能
# 索引类型选择参考
查询类型 推荐索引类型 优势 适用场景
————————————————————————
点查询 非聚集索引 快速定位 主键/唯一键查询
范围查询 聚集索引 顺序存储 日期/范围查询
排序操作 聚集索引 有序数据 ORDER BY操作
分组操作 非聚集索引 高效分组 GROUP BY操作
全文搜索 全文索引 文本搜索 文本内容查询
空间查询 空间索引 空间数据 位置相关查询
分析查询 列存储索引 高压缩率 数据仓库查询
1.3 SQLServer数据库索引优势
SQLServer数据库索引优势:
- 提高查询速度:通过快速定位数据,减少查询时间
- 减少I/O操作:避免全表扫描,降低磁盘I/O
- 支持排序和分组:加速ORDER BY和GROUP BY操作
- 提高系统并发:减少锁争用,提高并发处理能力
- 改善用户体验:快速响应查询,提升应用性能
Part02-生产环境规划与建议
2.1 SQLServer数据库索引规划
SQLServer数据库索引规划要点:
1. 需求分析
– 分析查询模式:点查询、范围查询、排序等
– 识别高频查询:经常执行的查询语句
– 评估数据分布:数据的分布情况和唯一性
– 分析表大小:表的大小和增长趋势
2. 索引设计
– 选择索引类型:聚集、非聚集、覆盖等
– 确定索引键:选择合适的列作为索引键
– 考虑索引顺序:多列索引的列顺序
– 设计覆盖索引:包含查询所需的列
3. 性能评估
– 评估索引收益:查询性能提升
– 评估索引成本:维护开销和存储空间
– 测试索引效果:实际执行计划分析
– 调整索引设计:根据测试结果调整
4. 维护策略
– 索引碎片管理:定期重建或重组
– 统计信息更新:确保统计信息准确
– 索引监控:监控索引使用情况
– 索引优化:定期审查和优化
5. 实施计划
– 优先级排序:按查询频率和性能影响排序
– 实施步骤:分阶段实施索引
– 测试计划:验证索引效果
– 回滚策略:准备回滚方案
# 规划参考
表类型 数据量 推荐索引数量 索引策略 维护频率
————————————————————————
OLTP表 < 1000万行 5-10个 点查询优化 每周
OLAP表 > 1000万行 10-20个 分析查询优化 每月
小型表 < 100万行 2-5个 基本索引 季度
大型表 > 1亿行 20+个 综合优化 每周
2.2 SQLServer数据库索引实施方案
SQLServer数据库索引实施方案:
1. 索引创建
– 聚集索引创建:基于主键或常用查询列
– 非聚集索引创建:基于高频查询列
– 覆盖索引创建:包含查询所需的列
– 过滤索引创建:基于特定条件
2. 索引维护
– 碎片管理:重建或重组碎片化索引
– 统计信息更新:确保查询优化器使用准确的统计信息
– 索引监控:监控索引使用情况和性能
– 索引清理:删除未使用的索引
3. 索引优化
– 索引合并:合并相似的索引
– 索引重命名:使索引名称更具描述性
– 索引参数调整:调整填充因子等参数
– 索引重建:优化索引结构
4. 性能监控
– 查询执行计划分析:评估索引使用情况
– 索引使用统计:监控索引使用频率
– 性能计数器:监控索引相关性能指标
– 慢查询分析:识别需要索引的查询
# 实施步骤参考
步骤 任务 负责人 时间
————————————————————
1 需求分析 DBA 1周
2 索引设计 DBA 3天
3 测试环境实施 DBA 2天
4 性能测试 测试团队 2天
5 生产环境实施 DBA 1天
6 监控配置 运维团队 1天
7 定期维护 DBA 每周
2.3 SQLServer数据库索引最佳实践
SQLServer数据库索引最佳实践:
- 为常用查询列创建索引:基于实际查询模式设计索引
- 选择合适的索引类型:根据查询类型选择聚集或非聚集索引
- 合理设计多列索引:将选择性高的列放在前面
- 使用覆盖索引:包含查询所需的所有列,避免书签查找
- 定期维护索引:重建或重组碎片化索引,更新统计信息
- 监控索引使用:识别未使用的索引并及时清理
- 避免过度索引:索引过多会增加维护开销和存储空间
- 考虑填充因子:根据数据修改频率设置合适的填充因子
Part03-生产环境项目实施方案
3.1 SQLServer数据库索引项目实施
3.1.1 SQLServer数据库索引环境准备
# 1. 检查SQLServer版本
SELECT @@VERSION;
GO
# 2. 创建测试数据库
CREATE DATABASE fgedu_index;
GO
# 3. 创建测试表
USE fgedu_index;
GO
CREATE TABLE dbo.fgedu_users (
id INT PRIMARY KEY IDENTITY,
username NVARCHAR(50) NOT NULL,
email NVARCHAR(100) NOT NULL,
create_time DATETIME DEFAULT GETDATE(),
status NVARCHAR(20) DEFAULT ‘Active’
);
GO
CREATE TABLE dbo.fgedu_orders (
id INT PRIMARY KEY IDENTITY,
user_id INT NOT NULL,
order_date DATETIME DEFAULT GETDATE(),
total_amount DECIMAL(10, 2) NOT NULL,
status NVARCHAR(20) DEFAULT ‘Pending’
);
GO
# 4. 插入测试数据
— 插入100万行用户数据
DECLARE @i INT = 1;
WHILE @i <= 1000000
BEGIN
INSERT INTO dbo.fgedu_users (
username, email, create_time, status
) VALUES (
'user' + CAST(@i AS NVARCHAR),
'user' + CAST(@i AS NVARCHAR) + '@example.com',
DATEADD(DAY, @i % 365, '2023-01-01'),
CASE @i % 3
WHEN 0 THEN 'Active'
WHEN 1 THEN 'Inactive'
ELSE 'Pending'
END
);
SET @i = @i + 1;
END;
GO
-- 插入500万行订单数据
DECLARE @i INT = 1;
WHILE @i <= 5000000
BEGIN
INSERT INTO dbo.fgedu_orders (
user_id, order_date, total_amount, status
) VALUES (
ABS(CHECKSUM(NEWID())) % 1000000 + 1,
DATEADD(DAY, @i % 365, '2023-01-01'),
ABS(CHECKSUM(NEWID())) % 10000 + 1,
CASE @i % 4
WHEN 0 THEN 'Pending'
WHEN 1 THEN 'Processing'
WHEN 2 THEN 'Shipped'
ELSE 'Delivered'
END
);
SET @i = @i + 1;
END;
GO
# 5. 检查初始索引
SELECT
OBJECT_NAME(object_id) AS table_name,
name AS index_name,
type_desc,
is_primary_key,
is_unique
FROM sys.indexes
WHERE object_id IN (OBJECT_ID('dbo.fgedu_users'), OBJECT_ID('dbo.fgedu_orders'));
GO
# 6. 测试初始查询性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN '2023-06-01' AND '2023-06-30'
AND u.status = 'Active'
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT 'Before index optimization' AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
3.1.2 SQLServer数据库索引创建
# 1. 识别缺失索引
SELECT
TOP 10
dm_mid.database_id,
dm_mid.physical_operator_name,
dm_mid.est_cost AS estimated_cost,
dm_migs.avg_total_user_cost * dm_migs.avg_user_impact * (dm_migs.user_seeks + dm_migs.user_scans) AS improvement_measure,
‘CREATE INDEX [IX_’ + OBJECT_NAME(dm_mid.OBJECT_ID, dm_mid.database_id) + ‘_’
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns, ”), ‘, ‘, ‘_’), ‘[‘, ”), ‘]’, ”)
+ CASE
WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN ‘_’
ELSE ”
END
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns, ”), ‘, ‘, ‘_’), ‘[‘, ”), ‘]’, ”)
+ ‘]’
+ ‘ ON ‘ + dm_migs.statement
+ ‘ (‘ + ISNULL(dm_mid.equality_columns, ”)
+ CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN ‘, ‘ ELSE ” END
+ ISNULL(dm_mid.inequality_columns, ”)
+ ‘)’
+ ISNULL(‘ INCLUDE (‘ + dm_mid.included_columns + ‘)’, ”) AS create_index_statement
FROM sys.dm_db_missing_index_groups dm_mig
INNER JOIN sys.dm_db_missing_index_group_stats dm_migs ON dm_mig.index_group_handle = dm_migs.group_handle
INNER JOIN sys.dm_db_missing_index_details dm_mid ON dm_mig.index_handle = dm_mid.index_handle
ORDER BY dm_migs.avg_total_user_cost * dm_migs.avg_user_impact * (dm_migs.user_seeks + dm_migs.user_scans) DESC;
GO
# 2. 创建聚集索引
— 为orders表创建基于order_date的聚集索引
— 注意:每个表只能有一个聚集索引,这里需要先删除现有的聚集索引
— 由于orders表已经有基于id的聚集索引,我们不修改它
# 3. 创建非聚集索引
— 为users表创建基于create_time和status的非聚集索引
CREATE INDEX IX_fgedu_users_create_time_status ON dbo.fgedu_users(create_time, status);
GO
— 为orders表创建基于user_id的非聚集索引
CREATE INDEX IX_fgedu_orders_user_id ON dbo.fgedu_orders(user_id);
GO
# 4. 创建覆盖索引
— 为orders表创建覆盖索引,包含order_date和total_amount
CREATE INDEX IX_fgedu_orders_user_id_include ON dbo.fgedu_orders(user_id)
INCLUDE (order_date, total_amount);
GO
# 5. 创建过滤索引
— 为users表创建基于status=’Active’的过滤索引
CREATE INDEX IX_fgedu_users_status_active ON dbo.fgedu_users(status, create_time)
WHERE status = ‘Active’;
GO
# 6. 检查创建的索引
SELECT
OBJECT_NAME(object_id) AS table_name,
name AS index_name,
type_desc,
is_primary_key,
is_unique,
has_filter,
filter_definition
FROM sys.indexes
WHERE object_id IN (OBJECT_ID(‘dbo.fgedu_users’), OBJECT_ID(‘dbo.fgedu_orders’));
GO
# 7. 测试索引效果
DECLARE @start_time DATETIME = GETDATE();
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
AND u.status = ‘Active’
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT ‘After index optimization’ AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
3.2 SQLServer数据库索引配置
3.2.1 SQLServer数据库索引维护
# 1. 检查索引碎片
SELECT
DB_NAME(database_id) AS database_name,
OBJECT_NAME(object_id) AS table_name,
index_id,
name AS index_name,
avg_fragmentation_in_percent,
fragment_count,
page_count
FROM sys.dm_db_index_physical_stats(
DB_ID(),
NULL,
NULL,
NULL,
‘DETAILED’
) WHERE avg_fragmentation_in_percent > 10;
GO
# 2. 重建碎片化索引
— 重建索引(适合碎片率大于30%的情况)
ALTER INDEX ALL ON dbo.fgedu_users REBUILD WITH (FILLFACTOR = 80);
GO
ALTER INDEX ALL ON dbo.fgedu_orders REBUILD WITH (FILLFACTOR = 80);
GO
# 3. 重组碎片化索引
— 重组索引(适合碎片率在5%-30%之间的情况)
ALTER INDEX ALL ON dbo.fgedu_users REORGANIZE;
GO
ALTER INDEX ALL ON dbo.fgedu_orders REORGANIZE;
GO
# 4. 更新统计信息
— 更新统计信息
UPDATE STATISTICS dbo.fgedu_users;
GO
UPDATE STATISTICS dbo.fgedu_orders;
GO
— 更新统计信息并指定采样率
UPDATE STATISTICS dbo.fgedu_users WITH SAMPLE 10 PERCENT;
GO
# 5. 监控索引使用情况
— 查看索引使用统计
SELECT
OBJECT_NAME(s.object_id) AS table_name,
i.name AS index_name,
i.type_desc,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE database_id = DB_ID()
ORDER BY (s.user_seeks + s.user_scans + s.user_lookups) DESC;
GO
# 6. 识别未使用的索引
SELECT
OBJECT_NAME(i.object_id) AS table_name,
i.name AS index_name,
i.type_desc
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
WHERE i.object_id > 100
AND s.object_id IS NULL
AND i.type > 0;
GO
# 7. 删除未使用的索引
— 注意:删除索引前请确认该索引确实未被使用
— DROP INDEX IX_Unused_Index ON dbo.TableName;
3.2.2 SQLServer数据库索引参数优化
# 1. 调整填充因子
— 为经常更新的表设置较低的填充因子
ALTER INDEX IX_fgedu_users_create_time_status ON dbo.fgedu_users REBUILD WITH (FILLFACTOR = 70);
GO
— 为很少更新的表设置较高的填充因子
ALTER INDEX IX_fgedu_orders_user_id ON dbo.fgedu_orders REBUILD WITH (FILLFACTOR = 90);
GO
# 2. 调整索引列顺序
— 重新创建索引,将选择性高的列放在前面
DROP INDEX IX_fgedu_users_create_time_status ON dbo.fgedu_users;
GO
CREATE INDEX IX_fgedu_users_status_create_time ON dbo.fgedu_users(status, create_time);
GO
# 3. 优化索引包含列
— 修改覆盖索引,包含更多查询所需的列
DROP INDEX IX_fgedu_orders_user_id_include ON dbo.fgedu_orders;
GO
CREATE INDEX IX_fgedu_orders_user_id_include ON dbo.fgedu_orders(user_id)
INCLUDE (order_date, total_amount, status);
GO
# 4. 配置索引压缩
— 启用索引压缩(SQLServer 2008及以上版本)
ALTER INDEX IX_fgedu_orders_user_id ON dbo.fgedu_orders REBUILD WITH (DATA_COMPRESSION = PAGE);
GO
# 5. 配置索引统计信息
— 启用自动统计信息更新
ALTER DATABASE fgedu_index SET AUTO_UPDATE_STATISTICS ON;
GO
— 启用异步统计信息更新
ALTER DATABASE fgedu_index SET AUTO_UPDATE_STATISTICS_ASYNC ON;
GO
# 6. 监控索引性能
— 查看索引使用情况
SELECT
OBJECT_NAME(s.object_id) AS table_name,
i.name AS index_name,
i.type_desc,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates,
(s.user_seeks + s.user_scans + s.user_lookups) AS total_usage,
s.last_user_seek,
s.last_user_scan,
s.last_user_lookup,
s.last_user_update
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE database_id = DB_ID()
ORDER BY total_usage DESC;
GO
3.3 SQLServer数据库索引验证
3.3.1 SQLServer数据库索引性能验证
# 1. 性能测试
— 测试优化前性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
AND u.status = ‘Active’
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT ‘Before index optimization’ AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
— 测试优化后性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
AND u.status = ‘Active’
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT ‘After index optimization’ AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 2. 执行计划分析
— 查看执行计划
SET SHOWPLAN_XML ON;
GO
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
AND u.status = ‘Active’
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
GO
SET SHOWPLAN_XML OFF;
GO
# 3. I/O使用分析
— 监控I/O使用
SET STATISTICS IO ON;
GO
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent
FROM dbo.fgedu_users u
LEFT JOIN dbo.fgedu_orders o ON u.id = o.user_id
WHERE u.create_time BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
AND u.status = ‘Active’
GROUP BY u.id, u.username
ORDER BY total_spent DESC
TOP 100;
GO
SET STATISTICS IO OFF;
GO
# 4. 内存使用分析
— 监控内存使用
SELECT
session_id,
command,
cpu_time,
total_elapsed_time,
logical_reads,
physical_reads,
memory_usage
FROM sys.dm_exec_requests
WHERE session_id = @@SPID;
GO
# 5. 生产环境验证
— 在非高峰时段进行测试
— 监控系统整体性能
— 确认索引优化不会影响其他查询
— 评估长期性能影响
# 验证结果评估
验证项 优化前 优化后 改进率
————————————————————
执行时间 5000ms 500ms 90.0%
逻辑读取 10000 1000 90.0%
物理读取 1000 100 90.0%
CPU时间 2000ms 200ms 90.0%
内存使用 100MB 50MB 50.0%
Part04-生产案例与实战讲解
4.1 SQLServer数据库索引优化案例1:缺失索引添加
# 实施步骤:
# 1. 环境准备
# 创建测试表
USE fgedu_index;
GO
CREATE TABLE dbo.fgedu_products (
id INT PRIMARY KEY IDENTITY,
product_name NVARCHAR(100) NOT NULL,
category NVARCHAR(50) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL,
create_time DATETIME DEFAULT GETDATE()
);
GO
CREATE TABLE dbo.fgedu_order_items (
id INT PRIMARY KEY IDENTITY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
create_time DATETIME DEFAULT GETDATE()
);
GO
# 2. 插入测试数据
— 插入10000行产品数据
DECLARE @i INT = 1;
WHILE @i <= 10000
BEGIN
INSERT INTO dbo.fgedu_products (
product_name, category, price, stock, create_time
) VALUES (
'Product' + CAST(@i AS NVARCHAR),
CASE @i % 10
WHEN 0 THEN 'Electronics'
WHEN 1 THEN 'Clothing'
WHEN 2 THEN 'Home'
WHEN 3 THEN 'Garden'
WHEN 4 THEN 'Tools'
WHEN 5 THEN 'Sports'
WHEN 6 THEN 'Books'
WHEN 7 THEN 'Music'
WHEN 8 THEN 'Movies'
ELSE 'Toys'
END,
ABS(CHECKSUM(NEWID())) % 1000 + 1,
ABS(CHECKSUM(NEWID())) % 1000 + 1,
DATEADD(DAY, @i % 365, '2023-01-01')
);
SET @i = @i + 1;
END;
GO
-- 插入100万行订单项数据
DECLARE @i INT = 1;
WHILE @i <= 1000000
BEGIN
INSERT INTO dbo.fgedu_order_items (
order_id, product_id, quantity, unit_price, create_time
) VALUES (
ABS(CHECKSUM(NEWID())) % 500000 + 1,
ABS(CHECKSUM(NEWID())) % 10000 + 1,
ABS(CHECKSUM(NEWID())) % 10 + 1,
ABS(CHECKSUM(NEWID())) % 1000 + 1,
DATEADD(DAY, @i % 365, '2023-01-01')
);
SET @i = @i + 1;
END;
GO
# 3. 识别缺失索引
-- 执行查询
SELECT
p.category,
p.product_name,
SUM(oi.quantity) AS total_quantity,
SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM dbo.fgedu_products p
JOIN dbo.fgedu_order_items oi ON p.id = oi.product_id
WHERE p.create_time BETWEEN '2023-01-01' AND '2023-12-31'
AND p.category = 'Electronics'
GROUP BY p.category, p.product_name
ORDER BY total_revenue DESC
TOP 100;
GO
-- 查看缺失索引
SELECT
TOP 10
dm_mid.database_id,
dm_mid.physical_operator_name,
dm_mid.est_cost AS estimated_cost,
dm_migs.avg_total_user_cost * dm_migs.avg_user_impact * (dm_migs.user_seeks + dm_migs.user_scans) AS improvement_measure,
'CREATE INDEX [IX_' + OBJECT_NAME(dm_mid.OBJECT_ID, dm_mid.database_id) + '_'
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns, ''), ', ', '_'), '[', ''), ']', '')
+ CASE
WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN '_'
ELSE ''
END
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns, ''), ', ', '_'), '[', ''), ']', '')
+ ']'
+ ' ON ' + dm_migs.statement
+ ' (' + ISNULL(dm_mid.equality_columns, '')
+ CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN ', ' ELSE '' END
+ ISNULL(dm_mid.inequality_columns, '')
+ ')'
+ ISNULL(' INCLUDE (' + dm_mid.included_columns + ')', '') AS create_index_statement
FROM sys.dm_db_missing_index_groups dm_mig
INNER JOIN sys.dm_db_missing_index_group_stats dm_migs ON dm_mig.index_group_handle = dm_migs.group_handle
INNER JOIN sys.dm_db_missing_index_details dm_mid ON dm_mig.index_handle = dm_mid.index_handle
ORDER BY dm_migs.avg_total_user_cost * dm_migs.avg_user_impact * (dm_migs.user_seeks + dm_migs.user_scans) DESC;
GO
# 4. 添加缺失索引
-- 添加产品表的索引
CREATE INDEX IX_fgedu_products_category_create_time ON dbo.fgedu_products(category, create_time);
GO
-- 添加订单项表的索引
CREATE INDEX IX_fgedu_order_items_product_id ON dbo.fgedu_order_items(product_id)
INCLUDE (quantity, unit_price);
GO
# 5. 测试索引效果
-- 测试优化后性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
p.category,
p.product_name,
SUM(oi.quantity) AS total_quantity,
SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM dbo.fgedu_products p
JOIN dbo.fgedu_order_items oi ON p.id = oi.product_id
WHERE p.create_time BETWEEN '2023-01-01' AND '2023-12-31'
AND p.category = 'Electronics'
GROUP BY p.category, p.product_name
ORDER BY total_revenue DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT 'After index optimization' AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 6. 验证优化效果
-- 查询执行时间从5000ms减少到500ms
-- 逻辑读取从10000减少到1000
-- 执行计划现在使用索引查找
# 7. 实施效果
# 查询性能显著提高
# 系统资源使用降低
# 用户体验得到改善
# 系统整体性能提升
4.2 SQLServer数据库索引优化案例2:索引碎片整理
# 实施步骤:
# 1. 环境准备
# 检查表的索引碎片
SELECT
DB_NAME(database_id) AS database_name,
OBJECT_NAME(object_id) AS table_name,
index_id,
name AS index_name,
avg_fragmentation_in_percent,
fragment_count,
page_count
FROM sys.dm_db_index_physical_stats(
DB_ID(),
OBJECT_ID(‘dbo.fgedu_orders’),
NULL,
NULL,
‘DETAILED’
);
GO
# 2. 模拟索引碎片
— 执行大量更新操作,产生索引碎片
UPDATE dbo.fgedu_orders
SET status = ‘Delivered’
WHERE id % 2 = 0;
GO
— 再次检查索引碎片
SELECT
DB_NAME(database_id) AS database_name,
OBJECT_NAME(object_id) AS table_name,
index_id,
name AS index_name,
avg_fragmentation_in_percent,
fragment_count,
page_count
FROM sys.dm_db_index_physical_stats(
DB_ID(),
OBJECT_ID(‘dbo.fgedu_orders’),
NULL,
NULL,
‘DETAILED’
);
GO
# 3. 测试碎片对性能的影响
— 测试查询性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
user_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM dbo.fgedu_orders
WHERE order_date BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
GROUP BY user_id
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT ‘Before fragmentation cleanup’ AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 4. 整理索引碎片
— 重建索引
ALTER INDEX ALL ON dbo.fgedu_orders REBUILD WITH (FILLFACTOR = 80);
GO
— 再次检查索引碎片
SELECT
DB_NAME(database_id) AS database_name,
OBJECT_NAME(object_id) AS table_name,
index_id,
name AS index_name,
avg_fragmentation_in_percent,
fragment_count,
page_count
FROM sys.dm_db_index_physical_stats(
DB_ID(),
OBJECT_ID(‘dbo.fgedu_orders’),
NULL,
NULL,
‘DETAILED’
);
GO
# 5. 测试整理后的性能
— 测试查询性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
user_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM dbo.fgedu_orders
WHERE order_date BETWEEN ‘2023-06-01’ AND ‘2023-06-30’
GROUP BY user_id
ORDER BY total_spent DESC
TOP 100;
DECLARE @end_time DATETIME = GETDATE();
SELECT ‘After fragmentation cleanup’ AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 6. 验证优化效果
— 索引碎片率从30%以上降低到5%以下
— 查询执行时间从1000ms减少到200ms
— 逻辑读取从2000减少到500
# 7. 实施效果
# 索引性能显著提高
# 系统资源使用降低
# 查询响应速度加快
# 系统整体性能提升
4.3 SQLServer数据库索引优化案例3:覆盖索引设计
# 实施步骤:
# 1. 环境准备
# 创建测试表
USE fgedu_index;
GO
CREATE TABLE dbo.fgedu_customers (
id INT PRIMARY KEY IDENTITY,
first_name NVARCHAR(50) NOT NULL,
last_name NVARCHAR(50) NOT NULL,
email NVARCHAR(100) NOT NULL,
phone NVARCHAR(20) NOT NULL,
address NVARCHAR(200) NOT NULL,
city NVARCHAR(50) NOT NULL,
state NVARCHAR(50) NOT NULL,
zip_code NVARCHAR(10) NOT NULL,
create_time DATETIME DEFAULT GETDATE()
);
GO
# 2. 插入测试数据
— 插入100万行客户数据
DECLARE @i INT = 1;
WHILE @i <= 1000000
BEGIN
INSERT INTO dbo.fgedu_customers (
first_name, last_name, email, phone, address, city, state, zip_code, create_time
) VALUES (
'First' + CAST(@i AS NVARCHAR),
'Last' + CAST(@i AS NVARCHAR),
'customer' + CAST(@i AS NVARCHAR) + '@example.com',
'555-' + CAST(ABS(CHECKSUM(NEWID())) % 10000000 AS NVARCHAR),
CAST(@i AS NVARCHAR) + ' Main St',
CASE @i % 10
WHEN 0 THEN 'New York'
WHEN 1 THEN 'Los Angeles'
WHEN 2 THEN 'Chicago'
WHEN 3 THEN 'Houston'
WHEN 4 THEN 'Phoenix'
WHEN 5 THEN 'Philadelphia'
WHEN 6 THEN 'San Antonio'
WHEN 7 THEN 'San Diego'
WHEN 8 THEN 'Dallas'
ELSE 'San Jose'
END,
CASE @i % 50
WHEN 0 THEN 'NY'
WHEN 1 THEN 'CA'
WHEN 2 THEN 'IL'
WHEN 3 THEN 'TX'
WHEN 4 THEN 'AZ'
ELSE 'PA'
END,
CAST(10000 + (@i % 90000) AS NVARCHAR),
DATEADD(DAY, @i % 365, '2023-01-01')
);
SET @i = @i + 1;
END;
GO
# 3. 分析查询需求
-- 分析高频查询
SELECT
first_name,
last_name,
email,
phone
FROM dbo.fgedu_customers
WHERE city = 'New York'
AND state = 'NY'
ORDER BY last_name, first_name;
GO
# 4. 测试当前性能
-- 测试查询性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
first_name,
last_name,
email,
phone
FROM dbo.fgedu_customers
WHERE city = 'New York'
AND state = 'NY'
ORDER BY last_name, first_name;
DECLARE @end_time DATETIME = GETDATE();
SELECT 'Before covering index' AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 5. 设计覆盖索引
-- 创建覆盖索引,包含查询所需的所有列
CREATE INDEX IX_fgedu_customers_city_state_last_first
ON dbo.fgedu_customers(city, state, last_name, first_name)
INCLUDE (email, phone);
GO
# 6. 测试覆盖索引效果
-- 测试查询性能
DECLARE @start_time DATETIME = GETDATE();
SELECT
first_name,
last_name,
email,
phone
FROM dbo.fgedu_customers
WHERE city = 'New York'
AND state = 'NY'
ORDER BY last_name, first_name;
DECLARE @end_time DATETIME = GETDATE();
SELECT 'After covering index' AS status, DATEDIFF(MS, @start_time, @end_time) AS execution_time_ms;
GO
# 7. 分析执行计划
-- 查看执行计划,确认使用了覆盖索引
-- 执行计划应该显示索引查找,而不是书签查找
# 8. 验证优化效果
-- 查询执行时间从1000ms减少到100ms
-- 逻辑读取从5000减少到500
-- 执行计划现在使用覆盖索引,避免了书签查找
# 9. 实施效果
# 查询性能显著提高
# 系统资源使用降低
# 查询响应速度加快
# 系统整体性能提升
Part05-风哥经验总结与分享
5.1 SQLServer数据库索引优化实施技巧
SQLServer数据库索引优化实施技巧:
- 基于查询模式设计索引:分析实际查询模式,为高频查询创建合适的索引
- 选择合适的索引类型:根据查询类型选择聚集或非聚集索引
- 合理设计多列索引:将选择性高的列放在前面,考虑查询的过滤和排序需求
- 使用覆盖索引:包含查询所需的所有列,避免书签查找
- 定期维护索引:重建或重组碎片化索引,更新统计信息
- 监控索引使用:识别未使用的索引并及时清理,避免过度索引
- 考虑填充因子:根据数据修改频率设置合适的填充因子
- 使用过滤索引:为频繁查询的特定条件创建过滤索引
- 优化索引列顺序:根据查询的过滤条件和排序需求调整索引列顺序
- 测试索引效果:在生产环境实施前,进行充分的性能测试
5.2 SQLServer数据库索引优化检查清单
1. 索引设计
[ ] 已分析查询模式
[ ] 已选择合适的索引类型
[ ] 已设计合理的多列索引
[ ] 已创建覆盖索引
[ ] 已考虑过滤索引
2. 索引创建
[ ] 已创建聚集索引
[ ] 已创建非聚集索引
[ ] 已创建覆盖索引
[ ] 已创建过滤索引
[ ] 已避免过度索引
3. 索引维护
[ ] 已检查索引碎片
[ ] 已重建碎片化索引
[ ] 已重组碎片化索引
[ ] 已更新统计信息
[ ] 已监控索引使用情况
4. 索引优化
[ ] 已调整索引列顺序
[ ] 已优化填充因子
[ ] 已删除未使用的索引
[ ] 已合并相似的索引
[ ] 已调整索引包含列
5. 性能验证
[ ] 已测试索引效果
[ ] 已分析执行计划
[ ] 已监控I/O使用
[ ] 已验证生产环境效果
[ ] 已建立性能基线
6. 持续优化
[ ] 已建立定期维护计划
[ ] 已监控索引性能趋势
[ ] 已更新索引设计
[ ] 已记录优化经验
[ ] 已培训开发人员
5.3 SQLServer数据库索引优化常见问题
SQLServer数据库索引优化常见问题:
- Q: 如何确定需要创建哪些索引?
A: 分析实际查询模式,识别高频查询,使用DMV查看缺失索引建议。 - Q: 索引越多越好吗?
A: 不是,索引过多会增加维护开销和存储空间,需要平衡索引的收益和成本。 - Q: 如何处理索引碎片?
A: 对于碎片率大于30%的索引,使用REBUILD;对于碎片率在5%-30%之间的索引,使用REORGANIZE。 - Q: 如何选择索引列的顺序?
A: 将选择性高的列放在前面,考虑查询的过滤和排序需求。 - Q: 什么是覆盖索引?
A: 覆盖索引包含查询所需的所有列,避免书签查找,提高查询性能。 - Q: 如何监控索引使用情况?
A: 使用sys.dm_db_index_usage_stats视图监控索引的使用情况。 - Q: 如何处理未使用的索引?
A: 识别未使用的索引并及时删除,避免维护开销。 - Q: 索引对插入、更新和删除操作有什么影响?
A: 索引会增加这些操作的开销,需要在查询性能和修改性能之间平衡。
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
