SQL Server 性能优化
大约 13 分钟约 3775 字
SQL Server 性能优化
简介
SQL Server 性能优化是数据库运维和开发中的核心技能,涉及执行计划分析、索引策略、等待统计分析和系统资源配置等多个方面。良好的性能优化不仅能显著提升查询响应速度,还能降低服务器资源消耗,提高系统整体吞吐量。本文将从实际运维角度出发,系统地介绍 SQL Server 性能优化的关键技术和工具。
特点
执行计划分析
执行计划是 SQL Server 查询优化器为执行查询生成的详细方案,通过分析执行计划可以定位性能瓶颈。
查看执行计划
-- 查看预估执行计划
SET SHOWPLAN_TEXT ON;
GO
SELECT o.OrderID, o.OrderDate, c.CustomerName, od.Quantity, od.UnitPrice
FROM Orders o
INNER JOIN Customers c ON o.CustomerID = c.CustomerID
INNER JOIN OrderDetails od ON o.OrderID = od.OrderID
WHERE o.OrderDate >= '2024-01-01'
AND c.Region = '华东'
ORDER BY o.OrderDate DESC;
GO
SET SHOWPLAN_TEXT OFF;
GO
-- 查看实际执行计划(SSMS 中使用快捷键 Ctrl+M)
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT o.OrderID, o.OrderDate, c.CustomerName
FROM Orders o
INNER JOIN Customers c ON o.CustomerID = c.CustomerID
WHERE o.OrderDate BETWEEN '2024-01-01' AND '2024-12-31'
OPTION (RECOMPILE);
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;常见执行计划操作符及优化建议
| 操作符 | 说明 | 潜在问题 | 优化建议 |
|---|---|---|---|
| Table Scan | 全表扫描 | 大表性能差 | 添加合适的索引 |
| Clustered Index Scan | 聚集索引扫描 | 范围过大 | 优化谓词条件 |
| Key Lookup | 键值查找 | 大量随机 IO | 使用覆盖索引 |
| Hash Match | 哈希匹配 | 内存消耗大 | 确保连接列有索引 |
| Sort | 排序操作 | CPU 和内存消耗 | 添加排序索引 |
| Spool | 临时存储 | TempDB 压力 | 优化查询减少中间结果 |
使用 Query Store 分析性能
-- 启用 Query Store
ALTER DATABASE ProductionDB
SET QUERY_STORE = ON (
OPERATION_MODE = READ_WRITE,
MAX_STORAGE_SIZE_MB = 1024,
QUERY_CAPTURE_MODE = AUTO,
SIZE_BASED_CLEANUP_MODE = AUTO,
WAIT_STATS_CAPTURE_MODE = ON
);
-- 查找最近性能下降的查询
SELECT
qs.query_id,
qt.query_sql_text,
rs.avg_duration / 1000 AS avg_duration_ms,
rs.avg_cpu_time / 1000 AS avg_cpu_ms,
rs.avg_logical_io_reads,
rs.count_executions,
p.last_force_failure_reason_desc
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
JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE rsi.start_time >= DATEADD(HOUR, -24, GETUTCDATE())
AND rs.avg_duration > 5000000 -- 超过5秒
ORDER BY rs.avg_duration DESC;
-- 强制使用特定执行计划
EXEC sp_query_store_force_plan @query_id = 123, @plan_id = 456;索引优化
索引是提升查询性能最有效的手段,合理的索引策略可以大幅减少 IO 操作。
创建优化索引
-- 创建覆盖索引避免 Key Lookup
CREATE NONCLUSTERED INDEX IX_Orders_Date_Customer
ON dbo.Orders (OrderDate DESC, CustomerID)
INCLUDE (OrderID, TotalAmount, Status);
-- 创建筛选索引(只索引活跃数据)
CREATE NONCLUSTERED INDEX IX_Orders_Active
ON dbo.Orders (CustomerID, OrderDate)
WHERE Status IN ('Pending', 'Processing', 'Shipped');
-- 创建列存储索引(适合分析查询)
CREATE NONCLUSTERED COLUMNSTORE INDEX IX_Orders_ColumnStore
ON dbo.OrderDetails (OrderID, ProductID, Quantity, UnitPrice, Discount);
-- 查看索引使用情况
SELECT
OBJECT_NAME(i.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.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
WHERE OBJECT_NAME(i.object_id) IN ('Orders', 'OrderDetails', 'Customers')
AND s.database_id = DB_ID()
ORDER BY (s.user_seeks + s.user_scans + s.user_lookups) DESC;索引维护
-- 查看索引碎片
SELECT
OBJECT_NAME(ips.object_id) AS table_name,
i.name AS index_name,
ips.index_type_desc,
ips.avg_fragmentation_in_percent,
ips.page_count,
CASE
WHEN ips.avg_fragmentation_in_percent > 30 THEN 'REBUILD'
WHEN ips.avg_fragmentation_in_percent BETWEEN 10 AND 30 THEN 'REORGANIZE'
ELSE 'OK'
END AS recommended_action
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
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
AND ips.page_count > 1000
ORDER BY ips.avg_fragmentation_in_percent DESC;
-- 重建索引(ONLINE 模式不阻塞业务)
ALTER INDEX IX_Orders_Date_Customer ON dbo.Orders
REBUILD WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, MAXDOP = 4);
-- 重组索引
ALTER INDEX IX_Orders_Date_Customer ON dbo.Orders REORGANIZE;等待统计分析
等待统计(Wait Stats)是诊断 SQL Server 性能瓶颈的重要手段,可以揭示系统资源争用的根源。
常用等待类型分析
-- 查看_top 等待类型
SELECT TOP 20
wait_type,
waiting_tasks_count,
wait_time_ms / 1000 AS wait_time_sec,
wait_time_ms / NULLIF(waiting_tasks_count, 0) / 1000 AS avg_wait_sec,
signal_wait_time_ms / 1000 AS signal_wait_sec,
CAST(100.0 * wait_time_ms / SUM(wait_time_ms) OVER() AS DECIMAL(5,2)) AS pct_total
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
'REQUEST_FOR_DEADLOCK_SEARCH', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_BUFFER_FLUSH', 'LAZYWRITER_SLEEP', 'XE_TIMER_EVENT',
'XE_DISPATCHER_WAIT', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE', 'BROKER_TO_FLUSH', 'BROKER_TASK_STOP',
'CLR_AUTO_EVENT', 'DIRTY_PAGE_POLL', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION'
)
ORDER BY wait_time_ms DESC;常见等待类型及处理方案
| 等待类型 | 含义 | 常见原因 | 处理方案 |
|---|---|---|---|
PAGEIOLATCH_xx | 页面 IO 等待 | 磁盘性能不足、缺失索引 | 优化索引、升级存储 |
LCK_M_xx | 锁等待 | 阻塞、事务过长 | 优化事务、降低隔离级别 |
CXPACKET | 并行等待 | 并行执行不均衡 | 调整 MAXDOP、优化查询 |
SOS_SCHEDULER_YIELD | CPU 调度等待 | CPU 资源紧张 | 优化 CPU 密集查询 |
WRITELOG | 日志写入等待 | 事务日志 IO 瓶颈 | 优化日志存储、减少事务 |
ASYNC_NETWORK_IO | 网络等待 | 客户端处理慢 | 优化客户端、分批返回 |
实时会话等待分析
-- 查看当前正在等待的会话
SELECT
s.session_id,
s.status,
s.blocking_session_id,
DB_NAME(s.database_id) AS database_name,
r.wait_type,
r.wait_time / 1000 AS wait_time_sec,
r.wait_resource,
r.cpu_time / 1000 AS cpu_time_sec,
r.reads,
r.writes,
r.logical_reads,
s.program_name,
s.host_name,
SUBSTRING(st.text, (r.statement_start_offset / 2) + 1,
CASE WHEN r.statement_end_offset = -1
THEN LEN(st.text)
ELSE (r.statement_end_offset - r.statement_start_offset) / 2
END) AS executing_sql
FROM sys.dm_exec_sessions s
JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.status = 'running'
AND r.wait_type IS NOT NULL
ORDER BY r.wait_time DESC;TempDB 优化
TempDB 是 SQL Server 的共享资源,合理配置 TempDB 对整体性能至关重要。
TempDB 配置建议
-- 查看 TempDB 当前配置
SELECT
name,
physical_name,
state_desc,
size * 8 / 1024 AS size_mb,
growth
FROM sys.master_files
WHERE database_id = DB_ID('tempdb');
-- 查看 TempDB 使用情况
SELECT
SUM(user_object_reserved_page_count) * 8 / 1024 AS user_objects_mb,
SUM(internal_object_reserved_page_count) * 8 / 1024 AS internal_objects_mb,
SUM(version_store_reserved_page_count) * 8 / 1024 AS version_store_mb,
SUM(unallocated_extent_page_count) * 8 / 1024 AS free_space_mb
FROM sys.dm_db_file_space_usage
WHERE database_id = DB_ID('tempdb');
-- 增加 TempDB 数据文件(建议与 CPU 核心数相同)
ALTER DATABASE tempdb ADD FILE (NAME = 'tempdev2', FILENAME = 'E:\SQLData\tempdb2.ndf', SIZE = 1GB, FILEGROWTH = 256MB);
ALTER DATABASE tempdb ADD FILE (NAME = 'tempdev3', FILENAME = 'E:\SQLData\tempdb3.ndf', SIZE = 1GB, FILEGROWTH = 256MB);
ALTER DATABASE tempdb ADD FILE (NAME = 'tempdev4', FILENAME = 'E:\SQLData\tempdb4.ndf', SIZE = 1GB, FILEGROWTH = 256MB);
-- 配置 TempDB 文件初始大小和增长
ALTER DATABASE tempdb
MODIFY FILE (NAME = 'tempdev', SIZE = 1GB, FILEGROWTH = 256MB);
ALTER DATABASE tempdb
MODIFY FILE (NAME = 'templog', SIZE = 512MB, FILEGROWTH = 256MB);TempDB 争用检测与解决
-- 检测 TempDB PFS/SGAM 争用
SELECT
waiting_tasks_count,
wait_duration_ms,
wait_type,
resource_description
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'PAGE%LATCH_%'
AND resource_description LIKE '2:%';
-- 查看 PFS 页面争用(resource_description 格式: dbid:fileid:pageid)
-- 2:1:1 = PFS 页面
-- 2:1:2 = GAM 页面
-- 2:1:3 = SGAM 页面
-- 解决方案:确保 TempDB 数据文件数量等于 CPU 逻辑核心数
-- 所有数据文件初始大小和增长率相同索引维护策略
索引碎片管理
-- 查看索引碎片情况
SELECT
OBJECT_NAME(i.object_id) AS table_name,
i.name AS index_name,
i.type_desc AS index_type,
ips.avg_fragmentation_in_percent,
ips.page_count,
ips.record_count,
CASE
WHEN ips.avg_fragmentation_in_percent > 30 THEN 'REBUILD'
WHEN ips.avg_fragmentation_in_percent > 10 THEN 'REORGANIZE'
ELSE 'OK'
END AS recommended_action
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.page_count > 1000 -- 只看超过 1000 页的索引
ORDER BY ips.avg_fragmentation_in_percent DESC;
-- 在线重建索引(不阻塞查询)
ALTER INDEX idx_orders_user_id ON Orders REBUILD WITH (ONLINE = ON, SORT_IN_TEMPDB = ON);
-- 离线重建(更快,但锁表)
ALTER INDEX idx_orders_user_id ON Orders REBUILD WITH (ONLINE = OFF);
-- 重组索引(轻量,适合碎片 10-30%)
ALTER INDEX idx_orders_user_id ON Orders REORGANIZE;
-- 禁用索引(重建前)
ALTER INDEX idx_orders_user_id ON Orders DISABLE;
ALTER INDEX idx_orders_user_id ON Orders REBUILD;
-- 索引维护策略:
-- 1. 每周检查碎片
-- 2. 碎片 > 30% → REBUILD
-- 3. 碎片 10-30% → REORGANIZE
-- 4. 碎片 < 10% → 不处理
-- 5. 大表索引维护在低峰期执行统计信息管理
-- 查看统计信息状态
SELECT
OBJECT_NAME(s.object_id) AS table_name,
s.name AS stats_name,
sp.last_updated,
sp.rows,
sp.rows_sampled,
sp.modification_counter,
CASE
WHEN sp.modification_counter > sp.rows * 0.2 THEN '需要更新'
ELSE '正常'
END AS status
FROM sys.stats s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1
ORDER BY sp.modification_counter DESC;
-- 手动更新统计信息
UPDATE STATISTICS Orders WITH FULLSCAN;
UPDATE STATISTICS Orders WITH SAMPLE 50 PERCENT;
UPDATE STATISTICS Orders; -- 默认采样
-- 更新所有统计信息
EXEC sp_updatestats;
-- 自动更新统计信息配置
ALTER DATABASE mydb SET AUTO_UPDATE_STATISTICS ON; -- 默认开启
ALTER DATABASE mydb SET AUTO_UPDATE_STATISTICS_ASYNC ON; -- 异步更新(推荐)
-- 创建过滤统计信息(针对条件查询优化)
CREATE STATISTICS stats_orders_paid
ON Orders(Status)
WHERE Status = 'paid';
-- 统计信息对执行计划的影响:
-- 统计信息过时 → 优化器选择错误的执行计划
-- 修改量超过 20% 触发自动更新
-- 大表全量更新统计信息很慢,考虑使用采样Query Store 实战
Query Store 配置与使用
-- 启用 Query Store
ALTER DATABASE mydb SET QUERY_STORE = ON;
ALTER DATABASE mydb SET QUERY_STORE (
OPERATION_MODE = READ_WRITE,
MAX_STORAGE_SIZE_MB = 1000,
INTERVAL_LENGTH_MINUTES = 10, -- 每 10 分钟聚合一次
STALE_QUERY_THRESHOLD_DAYS = 30, -- 30 天无执行标记为过期
SIZE_BASED_CLEANUP_MODE = AUTO, -- 空间不足自动清理
QUERY_CAPTURE_MODE = ALL -- 捕获所有查询
);
-- 查看性能最差的查询
SELECT TOP 20
q.query_id,
qt.query_sql_text,
rs.avg_duration / 1000000 AS avg_duration_ms,
rs.avg_cpu_time / 1000000 AS avg_cpu_ms,
rs.avg_logical_io_reads,
rs.execution_count,
rs.avg_logical_io_reads * rs.execution_count AS total_logical_reads
FROM sys.query_store_query q
INNER JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_runtime_stats rs ON q.query_id = rs.query_id
INNER JOIN sys.query_store_plan p ON q.query_id = p.query_id AND rs.plan_id = p.plan_id
WHERE rs.last_execution_time > DATEADD(DAY, -7, GETUTCDATE())
ORDER BY rs.avg_duration DESC;
-- 强制使用特定执行计划(修复参数嗅探)
-- 先找到好的执行计划 ID
SELECT plan_id, query_id, is_forced_plan
FROM sys.query_store_plan
WHERE query_id = 42;
-- 强制使用好的计划
EXEC sp_query_store_force_plan @query_id = 42, @plan_id = 7;
-- 取消强制计划
EXEC sp_query_store_unforce_plan @query_id = 42, @plan_id = 7;
-- 查看回归查询(性能突然变差的查询)
SELECT
rs.plan_id,
q.query_id,
qt.query_sql_text,
rs.avg_duration_first as first_avg_duration,
rs.avg_duration_last as last_avg_duration,
rs.avg_duration_first - rs.avg_duration_last AS regression_duration
FROM sys.query_store_query q
INNER JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
INNER JOIN (
SELECT
plan_id, query_id,
MIN(avg_duration) OVER (PARTITION BY query_id ORDER BY interval_start) AS avg_duration_first,
MAX(avg_duration) OVER (PARTITION BY query_id ORDER BY interval_start) AS avg_duration_last
FROM sys.query_store_runtime_stats_interval
) rs ON q.query_id = rs.query_id
WHERE rs.avg_duration_last > rs.avg_duration_first * 2 -- 性能下降超过 2 倍
ORDER BY regression_duration DESC;扩展事件
创建扩展事件会话
-- 创建扩展事件会话(轻量级性能监控)
CREATE EVENT SESSION [SlowQueryCapture] ON SERVER
ADD EVENT sqlserver.sp_statement_completed(
ACTION(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name,
sqlserver.sql_text,
sqlserver.username
)
WHERE (
[duration] > 5000000 -- 超过 5 秒
AND [database_name] = N'mydb'
)
),
ADD EVENT sqlserver.sql_batch_completed(
ACTION(
sqlserver.client_app_name,
sqlserver.sql_text
)
WHERE [duration] > 5000000
)
ADD TARGET package0.event_file(
SET filename = N'C:\SQLData\XEvents\SlowQueryCapture.xel',
max_file_size = 100, -- 100MB
max_rollover_files = 5 -- 保留 5 个文件
);
-- 启动会话
ALTER EVENT SESSION [SlowQueryCapture] ON SERVER STATE = START;
-- 查看事件数据
SELECT
event_data.value('(event/@timestamp)[1]', 'DATETIME2') AS event_time,
event_data.value('(event/data[@name="duration"]/value)[1]', 'BIGINT') / 1000000 AS duration_ms,
event_data.value('(event/data[@name="physical_reads"]/value)[1]', 'BIGINT') AS physical_reads,
event_data.value('(event/action[@name="sql_text"]/value)[1]', 'NVARCHAR(MAX)') AS sql_text,
event_data.value('(event/action[@name="client_app_name"]/value)[1]', 'NVARCHAR(200)') AS app_name,
event_data.value('(event/action[@name="client_hostname"]/value)[1]', 'NVARCHAR(200)') AS hostname
FROM sys.fn_xe_file_target_read_file(
'C:\SQLData\XEvents\SlowQueryCapture*.xel',
NULL, NULL, NULL
)
ORDER BY event_time DESC;
-- 停止和删除会话
ALTER EVENT SESSION [SlowQueryCapture] ON SERVER STATE = STOP;
DROP EVENT SESSION [SlowQueryCapture] ON SERVER;
-- 常用扩展事件场景:
-- 1. 死锁捕获:sqlserver.deadlock_graph
-- 2. 阻塞捕获:sqlserver.lock_timeout 超过阈值
-- 3. 等待统计:sqlserver.wait_info
-- 4. 内存使用:sqlserver.memory_grant_updated内存管理
内存配置与监控
-- 查看内存使用情况
SELECT
physical_memory_in_use_kb / 1024 AS physical_memory_mb,
total_page_allocations_kb / 1024 AS total_allocated_mb,
available_physical_memory_kb / 1024 AS available_memory_mb,
system_memory_state_desc,
process_physical_memory_low,
process_virtual_memory_low
FROM sys.dm_os_process_memory;
-- 缓冲池缓存命中率
SELECT
(SELECT COUNT(*) FROM sys.dm_os_buffer_descriptors WHERE is_modified = 0) AS clean_pages,
(SELECT COUNT(*) FROM sys.dm_os_buffer_descriptors WHERE is_modified = 1) AS dirty_pages,
ROUND(
(1.0 - (SELECT COUNT(*) FROM sys.dm_os_buffer_descriptors
WHERE database_id = DB_ID()
AND page_type = 'DATA_PAGE'
AND is_modified = 1) * 1.0 /
NULLIF((SELECT COUNT(*) FROM sys.dm_os_buffer_descriptors
WHERE database_id = DB_ID()
AND page_type = 'DATA_PAGE'), 0)) * 100, 2
) AS buffer_pool_hit_rate_pct;
-- 查看各数据库占用缓冲池
SELECT
DB_NAME(database_id) AS database_name,
COUNT(*) * 8 / 1024 AS buffer_pool_mb,
COUNT(*) AS pages_count
FROM sys.dm_os_buffer_descriptors
WHERE database_id > 0
GROUP BY database_id
ORDER BY buffer_pool_mb DESC;
-- 配置最大服务器内存
-- 建议:预留 10-20% 给操作系统和其他进程
-- 例如 32GB 服务器:max_server_memory = 26GB
EXEC sp_configure 'max server memory (MB)', 26624;
RECONFIGURE;
-- 内存不足的信号:
-- 1. 大量物理读取(缓存命中率下降)
-- 2. PAGEIOLATCH 等待增加
-- 3. 内存授予等待(RESOURCE_SEMAPHORE)
-- 4. 频繁的 lazy writer 活动优点
缺点
总结
SQL Server 性能优化是一个系统性工程,需要从执行计划分析、索引策略、等待统计和资源配置等多个维度综合考虑。建议建立常态化的性能监控机制,使用 Query Store 追踪查询性能变化趋势,定期进行索引维护和碎片整理。对于 TempDB,应遵循多文件配置原则,并持续关注 PFS/SGAM 争用问题。在优化过程中,务必先诊断再优化,避免盲目添加索引或修改配置导致新的性能问题。
关键知识点
- 数据库主题一定要同时看数据模型、读写模式和执行代价。
- 很多性能问题不是 SQL 语法问题,而是索引、统计信息、事务和数据分布问题。
- 高可用、备份、迁移和治理与查询优化同样重要。
- 先把数据模型、访问模式和执行代价绑定起来理解。
项目落地视角
- 所有优化前后都保留执行计划、样本 SQL 和关键指标对比。
- 上线前准备回滚脚本、备份点和校验方案。
- 把连接池、锁等待、慢查询和容量增长纳入日常巡检。
- 保留执行计划、样本 SQL、索引定义和优化前后指标。
常见误区
- 脱离真实数据分布讨论索引或分片。
- 只看单条 SQL,不看整条业务链路的事务和锁。
- 把测试环境结论直接等同于生产环境结论。
- 脱离真实数据分布设计索引。
进阶路线
- 继续向执行计划、存储引擎、复制机制和数据治理层深入。
- 把主题与 ORM、缓存、消息队列和归档策略联动起来思考。
- 沉淀成数据库设计规范、SQL 审核规则和变更流程。
- 继续深入存储引擎、复制机制、归档与冷热分层治理。
适用场景
- 当你准备把《SQL Server 性能优化》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合数据建模、查询优化、事务控制、高可用和迁移治理。
- 当系统开始遇到慢查询、锁冲突、热点数据或容量增长时,这类主题价值最高。
落地建议
- 先分析真实查询模式、数据量级和写入特征,再决定索引或分片策略。
- 所有优化结论都结合执行计划、样本数据和监控指标验证。
- 高风险操作前准备备份、回滚脚本与校验 SQL。
排错清单
- 先确认瓶颈在 CPU、I/O、锁等待、网络还是 SQL 本身。
- 检查执行计划是否走错索引、是否发生排序或全表扫描。
- 排查长事务、隐式类型转换、统计信息过期和参数嗅探。
复盘问题
- 如果把《SQL Server 性能优化》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《SQL Server 性能优化》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《SQL Server 性能优化》最大的收益和代价分别是什么?
