SQL Server性能优化 #

一、性能优化概述 #

1.1 优化方向 #

text
SQL Server性能优化方向:
├── 查询优化
│   ├── 执行计划分析
│   ├── 索引优化
│   └── SQL重写
├── 数据库设计优化
│   ├── 表设计
│   ├── 索引设计
│   └── 分区
├── 服务器优化
│   ├── 内存配置
│   ├── CPU配置
│   └── I/O配置
└── 应用优化
    ├── 连接池
    ├── 批处理
    └── 缓存

1.2 性能监控工具 #

text
SQL Server性能监控工具:
├── SQL Server Profiler
├── Extended Events
├── DMVs(动态管理视图)
├── Performance Monitor
├── Query Store
└── Activity Monitor

二、执行计划分析 #

2.1 查看执行计划 #

sql
-- 显示估计执行计划
-- SSMS: Ctrl+L

-- 显示实际执行计划
-- SSMS: Ctrl+M,然后执行查询

-- 使用命令查看
SET SHOWPLAN_TEXT ON;
GO
SELECT * FROM users WHERE id = 1;
GO
SET SHOWPLAN_TEXT OFF;
GO

-- XML格式执行计划
SET SHOWPLAN_XML ON;
GO
SELECT * FROM users WHERE id = 1;
GO
SET SHOWPLAN_XML OFF;
GO

2.2 执行计划解读 #

text
执行计划关键指标:
├── 扫描类型
│   ├── Table Scan(表扫描)→ 慢
│   ├── Index Scan(索引扫描)→ 中
│   └── Index Seek(索引查找)→ 快
├── 连接类型
│   ├── Nested Loops → 小数据量
│   ├── Hash Match → 大数据量
│   └── Merge Join → 已排序数据
├── 操作成本
│   ├── Estimated I/O Cost
│   ├── Estimated CPU Cost
│   └── Estimated Operator Cost
└── 行数估计
    ├── Estimated Number of Rows
    └── Actual Number of Rows

2.3 常见问题 #

sql
-- 表扫描(缺少索引)
SELECT * FROM users WHERE email = 'test@example.com';
-- 解决:创建索引

-- 索引扫描(索引不够优化)
SELECT * FROM users WHERE name LIKE '%test%';
-- 解决:使用前缀匹配或全文索引

-- 键查找(非聚集索引需要回表)
SELECT name, email FROM users WHERE email = 'test@example.com';
-- 解决:使用包含列索引

三、索引优化 #

3.1 缺失索引查询 #

sql
-- 查看缺失索引建议
SELECT 
    mid.statement AS table_name,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns,
    migs.user_seeks,
    migs.avg_user_impact
FROM sys.dm_db_missing_index_details mid
INNER JOIN sys.dm_db_missing_index_groups mig ON mid.index_handle = mig.index_handle
INNER JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
ORDER BY migs.avg_user_impact DESC;

-- 创建建议的索引
CREATE INDEX ix_users_email ON users(email);

3.2 索引使用分析 #

sql
-- 查看索引使用情况
SELECT 
    OBJECT_NAME(i.object_id) AS table_name,
    i.name AS index_name,
    i.type_desc,
    user_seeks,
    user_scans,
    user_lookups,
    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 OBJECT_NAME(i.object_id) = 'users';

-- 未使用的索引
SELECT 
    OBJECT_NAME(i.object_id) AS table_name,
    i.name AS index_name
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 s.index_id IS NULL AND i.type_desc = 'NONCLUSTERED';

3.3 索引碎片整理 #

sql
-- 查看索引碎片
SELECT 
    OBJECT_NAME(i.object_id) AS table_name,
    i.name AS index_name,
    ips.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
INNER JOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
WHERE ips.avg_fragmentation_in_percent > 10;

-- 重组索引(碎片5%-30%)
ALTER INDEX ix_users_email ON users REORGANIZE;

-- 重建索引(碎片>30%)
ALTER INDEX ix_users_email ON users REBUILD;

四、查询优化 #

4.1 查询重写 #

sql
-- 避免 SELECT *
-- 差
SELECT * FROM users;
-- 好
SELECT id, name, email FROM users;

-- 使用覆盖索引
CREATE INDEX ix_users_email_name ON users(email) INCLUDE (name);

SELECT email, name FROM users WHERE email = 'test@example.com';

-- 避免 OR(使用 UNION ALL)
-- 差
SELECT * FROM users WHERE id = 1 OR id = 2;
-- 好
SELECT * FROM users WHERE id = 1
UNION ALL
SELECT * FROM users WHERE id = 2;

-- 避免 LIKE '%xxx%'
-- 差
SELECT * FROM users WHERE name LIKE '%test%';
-- 好
SELECT * FROM users WHERE name LIKE 'test%';

-- 使用 EXISTS 代替 IN
-- 差
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 好
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

4.2 参数嗅探 #

sql
-- 参数嗅探问题
CREATE PROCEDURE usp_get_users @status BIT
AS
SELECT * FROM users WHERE status = @status;
GO

-- 解决方案1:使用OPTION (RECOMPILE)
CREATE PROCEDURE usp_get_users @status BIT
AS
SELECT * FROM users WHERE status = @status
OPTION (RECOMPILE);
GO

-- 解决方案2:使用局部变量
CREATE PROCEDURE usp_get_users @status BIT
AS
DECLARE @local_status BIT = @status;
SELECT * FROM users WHERE status = @local_status;
GO

-- 解决方案3:使用OPTIMIZE FOR
CREATE PROCEDURE usp_get_users @status BIT
AS
SELECT * FROM users WHERE status = @status
OPTION (OPTIMIZE FOR (@status = 1));
GO

4.3 查询提示 #

sql
-- 强制使用索引
SELECT * FROM users WITH (INDEX(ix_users_email))
WHERE email = 'test@example.com';

-- 强制使用连接类型
SELECT * FROM users u
INNER LOOP JOIN orders o ON u.id = o.user_id;

-- 使用NOLOCK
SELECT * FROM users WITH (NOLOCK);

-- 使用OPTION提示
SELECT * FROM users
OPTION (MAXDOP 1);  -- 限制并行度

五、统计信息 #

5.1 查看统计信息 #

sql
-- 查看统计信息
EXEC sp_helpstats 'users';

-- 查看统计信息详情
DBCC SHOW_STATISTICS ('users', 'ix_users_email');

-- 查看统计信息更新时间
SELECT 
    OBJECT_NAME(object_id) AS table_name,
    name AS stats_name,
    stats_date(object_id, stats_id) AS last_updated
FROM sys.stats;

5.2 更新统计信息 #

sql
-- 更新单个表的统计信息
UPDATE STATISTICS users;

-- 更新单个统计信息
UPDATE STATISTICS users ix_users_email;

-- 更新整个数据库的统计信息
EXEC sp_updatestats;

-- 自动更新统计信息
ALTER DATABASE mydb SET AUTO_UPDATE_STATISTICS ON;
ALTER DATABASE mydb SET AUTO_CREATE_STATISTICS ON;

六、等待统计 #

6.1 查看等待统计 #

sql
-- 查看等待统计
SELECT 
    wait_type,
    waiting_tasks_count,
    wait_time_ms,
    max_wait_time_ms,
    signal_wait_time_ms
FROM sys.dm_os_wait_stats
ORDER BY wait_time_ms DESC;

-- 常见等待类型:
-- PAGEIOLATCH_xx - I/O问题
-- CXPACKET - 并行问题
-- SOS_SCHEDULER_YIELD - CPU问题
-- LCK_xx - 锁问题
-- ASYNC_NETWORK_IO - 网络问题

6.2 性能计数器 #

sql
-- 查看性能计数器
SELECT 
    object_name,
    counter_name,
    cntr_value
FROM sys.dm_os_performance_counters
WHERE object_name LIKE '%Buffer Manager%'
   OR object_name LIKE '%Memory Manager%';

七、Query Store #

7.1 启用Query Store #

sql
-- 启用Query Store
ALTER DATABASE mydb SET QUERY_STORE = ON;

-- 配置Query Store
ALTER DATABASE mydb SET QUERY_STORE (
    OPERATION_MODE = READ_WRITE,
    CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),
    DATA_FLUSH_INTERVAL_SECONDS = 900,
    MAX_STORAGE_SIZE_MB = 100
);

7.2 使用Query Store #

sql
-- 查看查询性能
SELECT 
    q.query_id,
    qt.query_sql_text,
    rs.avg_duration,
    rs.avg_logical_io_reads,
    rs.count_executions
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
ORDER BY rs.avg_duration DESC;

-- 强制使用特定执行计划
EXEC sp_query_store_force_plan @query_id = 1, @plan_id = 1;

八、内存优化 #

8.1 内存配置 #

sql
-- 查看内存配置
SELECT 
    name,
    value,
    value_in_use
FROM sys.configurations
WHERE name LIKE '%memory%';

-- 设置最小/最大内存
EXEC sp_configure 'min server memory', 1024;
EXEC sp_configure 'max server memory', 4096;
RECONFIGURE;

8.2 内存使用分析 #

sql
-- 查看内存使用
SELECT 
    type,
    SUM(pages_kb) / 1024 AS size_mb
FROM sys.dm_os_memory_clerks
GROUP BY type
ORDER BY size_mb DESC;

-- Buffer Pool使用
SELECT 
    COUNT(*) * 8 / 1024 AS cached_pages_mb,
    COUNT(*) AS cached_pages
FROM sys.dm_os_buffer_descriptors;

九、最佳实践 #

9.1 查询优化建议 #

text
查询优化建议:
├── 只查询需要的列
├── 使用适当的索引
├── 避免函数操作列
├── 使用参数化查询
├── 避免游标
├── 使用SET NOCOUNT ON
└── 合理使用事务

9.2 索引优化建议 #

text
索引优化建议:
├── 为WHERE、JOIN、ORDER BY列创建索引
├── 使用覆盖索引
├── 定期维护索引碎片
├── 更新统计信息
├── 删除未使用的索引
└── 避免过度索引

十、总结 #

性能优化要点:

方面 工具/方法
执行计划 SSMS、SHOWPLAN
索引优化 DMVs、缺失索引
查询优化 重写、提示
统计信息 UPDATE STATISTICS
等待分析 sys.dm_os_wait_stats
Query Store 查询性能追踪

最佳实践:

  1. 分析执行计划
  2. 优化索引
  3. 更新统计信息
  4. 监控等待统计
  5. 定期维护

恭喜你完成了SQL Server完全指南的学习!

最后更新:2026-03-27