3. 业务负载特征分析与基线建立

数据库性能问题很少是“平均情况”的问题。真正让业务感知到卡顿、超时、报表跑不出来的,往往是每天固定时段的高峰并发、夜间批处理与报表查询之间的资源争抢。因此,性能优化的第一步不是急着改索引或调参数,而是把业务负载“看清楚”:它由哪些场景组成、各自在什么时间发生、消耗哪些资源、正常时应该是什么样子。这就是负载特征分析与性能基线建立。

一、把混合负载拆成可度量的场景

一个 SQL Server 实例上通常同时跑着多种负载,它们的特征差异极大。我们可以按“访问模式 + 时间窗口 + 资源偏好”三个维度把负载拆开:

  • 在线交易(OLTP)高峰:白天 9:00–12:00、14:00–18:00,短事务、高并发、大量小范围读写,对 CPU、锁与日志写入速率敏感,单条语句通常毫秒级。
  • 批处理:常在凌晨或业务低峰触发,长事务、大批量 INSERT/UPDATE/DELETE,对日志空间、tempdb、I/O 吞吐敏感,单批可能持续数分钟到数小时。
  • 报表与分析查询:可能在白天被业务人员随时发起,也可能集中在月末、月初,扫描大量数据、做聚合与排序,对内存授予、tempdb 和磁盘读带宽敏感。
  • 后台维护:索引重建、统计信息更新、备份、DBCC 检查,属于可控负载,但若与高峰重叠会显著放大争用。

拆分的目的是让每一类负载都有独立的度量口径。否则把 OLTP 的短事务和报表的大扫描混在一起看平均值,得到的“平均响应时间”既不能反映高峰体验,也不能定位批处理瓶颈。

二、采集哪些数据、从哪里采集

SQL Server 自带多套可用的采集入口,按“实时—短期—长期”分层使用最经济:

  • 实时快照:sys.dm_exec_requests、sys.dm_exec_sessions、sys.dm_os_wait_stats,用于抓当前正在跑的语句、等待类型和阻塞链。
  • 语句级累计:sys.dm_exec_query_stats 配合 sys.dm_exec_sql_text、sys.dm_exec_query_plan,可拿到每个语句的累计 CPU、逻辑读、执行次数、总耗时。
  • 历史趋势:Query Store 是建立基线的核心工具,它按时间区间保存查询的执行计划、运行时统计和等待类别,能回答“上周同一时段这条语句是什么表现”。
  • 性能计数器:PerfMon 或 sys.dm_os_performance_counters,采集 Batch Requests/sec、SQL Compilations/sec、Page Life Expectancy、Lock Waits/sec 等,用于判断整体压力水平。

采集频率要与负载节奏匹配:OLTP 高峰建议 15–30 秒一次快照,批处理窗口可放宽到 1–5 分钟,长期趋势用 Query Store 的 15 分钟或 1 小时聚合区间即可。采集本身也会消耗资源,务必避免在高并发时执行重量级 DMV 查询。

三、从原始数据到负载画像

采集到的数据需要按场景归类,形成可比较的画像。一个实用的做法是建立“时段 × 场景 × 指标”的矩阵。下面给出一个示例结构(数值为教学假设,非实测):

时段主导场景Batch Requests/sec平均等待类型CPU 使用率关键观察
09:00–12:00OLTP 高峰约 1800LCK_M_X、PAGEIOLATCH_SH65%–80%订单写入集中,锁等待上升
14:00–18:00OLTP + 报表约 1500CXPACKET、SOS_SCHEDULER_YIELD70%–90%报表并行查询抢占 CPU
01:00–04:00批处理约 200WRITELOG、PAGEIOLATCH_EX30%–50%日志写入与 tempdb 压力大
08:30–09:00报表集中约 400RESOURCE_SEMAPHORE40%–60%内存授予等待明显

这张矩阵的价值在于:它把“什么时候慢、慢在什么等待上、由哪类负载引起”三件事绑定在一起。例如看到 14:00–18:00 的 CXPACKET 上升,就能推断是报表的并行计划与 OLTP 争抢 CPU,而不是单纯的事务锁问题。

四、建立性能基线

基线不是某一个数字,而是一组“正常范围”。建立方法建议分三步:

  1. 选稳定窗口:挑选连续 2–4 周、业务量相对平稳、无重大变更的时段作为参考期。
  2. 按场景取分位数:对每类负载的关键指标取 P50、P95、P99,而不是只看平均值。例如 OLTP 核心语句 P95 响应时间 80ms、P99 200ms,就是比“平均 30ms”更有意义的基线。
  3. 记录伴随条件:基线必须带上版本、索引状态、统计信息更新时间、并发用户数等上下文,否则环境一变基线就失效。

基线建立后,日常监控只需对比“当前值是否落在基线区间内”。超出 P95 或 P99 阈值时触发告警,再结合等待类型和 Query Store 回归查询定位原因。

五、识别异常模式

有了基线,异常就有了参照。常见的异常模式包括:

  • 计划回归:同一语句执行次数不变,但 CPU 或逻辑读突然上升数倍,Query Store 中可见计划切换。
  • 参数嗅探:首次编译用典型值生成计划,后续传入非典型参数导致扫描代替查找,表现为同一语句耗时方差极大。
  • 阻塞链扩散:LCK_M_X 等待集中在少数会话,且阻塞源头是一条长事务或缺失索引的更新语句。
  • 批处理窗口漂移:批处理完成时间逐日推迟,说明数据量增长或计划退化正在累积。
  • 报表内存授予等待:RESOURCE_SEMAPHORE 上升,通常伴随大排序或哈希连接,需要检查内存授予与并发度。

下面这张图把“负载采集 → 场景归类 → 基线对比 → 异常识别”的完整链路和关键判断点画在一起,便于在实际排查时按图索骥。

业务负载特征分析与基线建立链路 采集入口 DMV 实时快照 Query Store 历史 PerfMon 计数器 扩展事件 / 跟踪 场景归类 OLTP 高峰 批处理窗口 报表 / 分析查询 后台维护任务 性能基线 P50 / P95 / P99 等待类型分布 CPU / I/O / 内存区间 版本与上下文记录 异常识别 计划回归 参数嗅探 阻塞扩散 窗口漂移 典型日负载时间轴 01:00 批处理 08:30 报表集中 09:00–12:00 OLTP 高峰 14:00–18:00 混合负载 22:00 维护窗口 当前指标是否落在基线区间? P95 响应时间 / 等待类型 / CPU 区间 对比 Query Store 同区间历史 超出阈值 → 定位异常模式 看等待类型:LCK / CXPACKET / RESOURCE_SEMAPHORE 看计划变化:是否发生回归或参数嗅探 是 基线维护闭环 业务量变化 / 版本升级 / 索引调整后,重新采集参考窗口并更新 P50、P95、P99 区间 基线不是一次性结果,而是随业务演进的动态参照

在实际操作中,建议把上述链路固化为一个可重复的流程:每天固定时间导出 Query Store 的 Top 资源消耗查询与等待统计,按场景归档;每周对比一次基线区间,标记出偏离项;每月复核一次基线本身是否仍然代表当前业务。这样,性能优化就从“出问题再救火”转变为“用数据提前发现趋势”。

基线建立初期不必追求指标齐全,先抓住每类负载的 2–3 个核心指标(如 OLTP 的 P95 响应时间与锁等待、批处理的日志写入速率与完成时长、报表的内存授予等待),比堆砌几十个计数器更容易落地。