SQL Server 迁移后的性能问题,常常不表现为“所有接口都变慢”。更常见的情况是:少量核心查询耗时上升、某个批处理窗口变长、CPU 利用率升高,或者只在特定参数下出现超时。
这类问题不应直接归因于“新环境性能差”。迁移同时可能改变数据库兼容级别、服务器级配置、数据分布、统计信息状态、索引维护状态和并发负载。即使业务 SQL 没有改动,优化器也可能选择不同的执行计划。
本文讨论的目标不是承诺迁移后一定提速,而是建立一条可复现的诊断链路:确认问题是否真实存在,找出变化发生在哪一层,并以可回滚的方式处理。
先定义可比较的性能基线
迁移前后若使用不同时间段、不同数据量或不同并发强度进行比较,结论没有参考价值。基线至少应包含以下维度:
- 关键业务操作及其固定输入参数,例如订单详情、报表汇总、库存扣减。
- 调用次数、平均耗时、P95 或 P99 耗时、失败数。
- 数据库侧 CPU 时间、逻辑读、物理读、返回行数。
- 采样窗口内的并发量与数据规模。
对于单条查询,SET STATISTICS IO, TIME ON 可以辅助分析资源消耗;但不要将其输出当作线上压测工具。更稳妥的方式是,在生产或准生产环境启用 Query Store,并以一段稳定业务窗口内的聚合数据作为比较依据。
可先确认目标数据库的版本和兼容级别:
SELECT
name,
compatibility_level,
recovery_model_desc
FROM sys.databases
WHERE name = N'AppDb';
兼容级别影响查询优化器可使用的行为。迁移到更高版本的 SQL Server 后,数据库仍可能保留旧兼容级别;反过来,直接升级兼容级别也可能让部分查询更换计划。因此,版本升级与兼容级别切换应拆成可观察、可回退的变更,而不是在同一个窗口内同时完成。
Query Store 如何帮助定位计划变化
Query Store 会持久化查询文本、计划和运行时统计信息,适合回答两个关键问题:一条查询是否确实变慢了?变慢时是否使用了不同计划?
在支持 Query Store 的 SQL Server 版本中,可先检查当前状态:
SELECT
actual_state_desc,
desired_state_desc,
current_storage_size_mb,
max_storage_size_mb,
query_capture_mode_desc
FROM sys.database_query_store_options;
如果尚未启用,需要结合版本能力、存储预算和变更流程评估后开启。下面是一个示例配置,具体选项应以当前 SQL Server 版本文档为准:
ALTER DATABASE AppDb
SET QUERY_STORE = ON;
GO
ALTER DATABASE AppDb
SET QUERY_STORE (
OPERATION_MODE = READ_WRITE,
QUERY_CAPTURE_MODE = AUTO,
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),
MAX_STORAGE_SIZE_MB = 1024
);
GO
启用后,不要马上根据某一次执行结果强制计划。先从运行时统计中筛选平均耗时或逻辑读较高、且执行次数足够的查询:
SELECT TOP (20)
q.query_id,
p.plan_id,
rs.count_executions,
CAST(rs.avg_duration / 1000.0 AS decimal(18, 2)) AS avg_duration_ms,
CAST(rs.avg_cpu_time / 1000.0 AS decimal(18, 2)) AS avg_cpu_ms,
rs.avg_logical_io_reads,
qt.query_sql_text
FROM sys.query_store_runtime_stats AS rs
JOIN sys.query_store_plan AS p
ON p.plan_id = rs.plan_id
JOIN sys.query_store_query AS q
ON q.query_id = p.query_id
JOIN sys.query_store_query_text AS qt
ON qt.query_text_id = q.query_text_id
ORDER BY rs.avg_duration DESC;
该查询只用于初步排序。avg_duration 会受到阻塞、I/O 抖动和采样区间的影响,不能单独证明计划存在问题。诊断时应进一步对比同一 query_id 的多个 plan_id,并确认慢计划与业务异常窗口是否重合。
四步排查迁移后性能回退
1. 排除环境和负载差异
先确认应用连接是否全部切换到目标实例,连接字符串中的数据库名、只读路由、加密设置和连接池配置是否符合预期。连接字符串中的密码不应写入代码或配置仓库,应从环境变量或受控密钥存储读取。
以 .NET 为例:
var connectionString = Environment.GetEnvironmentVariable("APP_DB_CONNECTION_STRING");
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException("APP_DB_CONNECTION_STRING is not configured.");
}
同时核对迁移前后数据行数、索引数量和数据库文件布局。数据缺失、索引漏建或日志文件异常增长,都可能伪装成查询性能问题。
2. 检查统计信息与索引健康度
优化器依赖统计信息估计筛选后的行数。全量导入、恢复备份、数据集中写入后,统计信息可能无法代表当前数据分布。对可疑表先检查统计信息更新时间:
SELECT
s.name AS statistic_name,
STATS_DATE(s.object_id, s.stats_id) AS last_updated
FROM sys.stats AS s
WHERE s.object_id = OBJECT_ID(N'dbo.Orders');
对于确认受影响的表,可在维护窗口内更新统计信息:
UPDATE STATISTICS dbo.Orders WITH FULLSCAN;
FULLSCAN 会增加维护开销,不能不加区分地用于所有大表。若表很大,应先评估执行时间、资源占用和现有维护策略;在多数场景下,定向更新受影响对象比全库盲目刷新更可控。
还要对比迁移前后的索引定义。建议使用脚本或数据库项目进行结构比对,不要只通过名称判断索引是否一致。键列顺序、包含列、筛选条件和唯一性任一不同,都可能影响计划选择。
3. 对比执行计划,但不要只看图形颜色
实际执行计划中的估计行数与实际行数差距较大,通常提示统计信息、参数敏感性或谓词写法存在问题。需要重点关注:
- 估计行数和实际行数持续出现数量级差异。
- 大量逻辑读,且访问路径从索引查找变为扫描。
- 内存授予明显偏大或偏小,并伴随排序、哈希操作溢写。
- 隐式类型转换导致索引条件无法有效使用。
参数敏感性是迁移后经常被误判的问题:同一参数化查询在不同参数值下本就可能需要不同策略。临时加入 OPTION (RECOMPILE) 虽能验证参数影响,但会改变编译与缓存行为,不宜作为默认的长期修复方案。
4. 以受控方式验证计划纠偏
当证据表明某个历史计划更稳定,且已确认 SQL 文本、架构与数据分布没有根本变化时,可以在低风险窗口内尝试强制该计划:
EXEC sys.sp_query_store_force_plan
@query_id = 42,
@plan_id = 107;
强制计划是缓解手段,不是根因分析的替代品。执行后应监控目标查询的耗时、CPU、逻辑读、错误率与阻塞情况,并记录变更时间。若效果不符合预期,可解除强制:
EXEC sys.sp_query_store_unforce_plan
@query_id = 42,
@plan_id = 107;
在生产环境中,任何强制计划操作都应有明确回滚条件。对于依赖频繁变化数据分布的查询,长期固定计划可能在未来重新成为风险。
常见问题
是否应该一迁移就升级兼容级别?
不建议把它当作默认动作。先在目标实例以原兼容级别验证功能和基线,再单独评估兼容级别升级,能够更清楚地区分实例迁移问题与优化器行为变化。具体可支持的兼容级别取决于目标 SQL Server 版本。
更新统计信息能解决所有慢查询吗?
不能。统计信息过期只是一个常见原因。阻塞、磁盘延迟、索引缺失、参数敏感性、应用端连接耗尽和错误的事务边界,都可能造成响应变慢。更新后必须重新测量,而不是把维护操作视为结论。
能否只依据缺失索引建议建索引?
不能直接照做。缺失索引 DMV 提供的是候选线索,不了解现有索引、写入成本和查询组合时,直接新增索引可能增加写放大与维护成本。应先检查是否可以调整已有复合索引或消除重复索引。
Query Store 占满空间会怎样?
其行为与配置和 SQL Server 版本有关,应通过 sys.database_query_store_options 持续监测状态和空间使用量。上线前需要设置合理容量与清理策略,并将 Query Store 状态纳入日常巡检。
总结
SQL Server 迁移后的性能诊断,核心不是寻找一个“万能参数”,而是让每个判断都有可比较的数据支撑。先控制变量建立基线,再利用 Query Store 识别异常查询与计划差异,随后检查统计信息、索引和环境配置,最后通过可回滚的变更验证假设。
这样处理的收益是将“迁移后感觉变慢”拆成可验证的问题:是负载不同、计划改变、数据分布变化,还是资源与阻塞因素导致。只有确认原因后再修复,才能避免用偶然有效的操作掩盖长期风险。