• Welcome to HiddenMerit - Clyde's Blog
  • Welcome to try the game Torn: Referral Link
  • If you are my relative, friend, or netizen, quickly press Ctrl+D to bookmark Clyde's Blog
  • This site has a like feature. If you read any article, please hit the like button so I know someone has visited
  • Email: hiddenmeritATgmail.com (replace AT with @)

第十期:性能监控与调优方法论 —— 从等待统计到DMV分析

DBA Clyde Jin 3周前 (04-25) 94次浏览 0个评论

第十期:性能监控与调优方法论 —— 从等待统计到DMV分析

1. 性能优化的核心思维:基于证据,而非直觉

三个关键转变

| 从 | 到 |

|—-|—-|

| “我觉得这里慢” | “等待统计显示这里花了X%时间” |

| “重启一下试试” | “定位具体瓶颈类型(CPU/IO/网络/锁)” |

| “加个索引吧” | “分析执行计划+等待类型后再决定” |

核心方法论Waits and Queues(等待与队列)

  • SQL Server 的性能问题最终都会表现为等待(某个资源不可用)

  • 找出TOP等待类型 → 定位瓶颈资源 → 针对性优化

2. 等待类型分类与解读

等待类型三大类(按可优化性排序):

| 类别 | 定义 | 典型等待 | 处理方向 |

|——|——|———-|———-|

| 资源等待 | 等待CPU、内存、I/O等硬件资源 | WRITELOG, PAGEIOLATCH_*, SOS_SCHEDULER_YIELD | 升级硬件或优化使用方式 |

| 锁等待 | 等待锁释放 | LCK<em>M</em>* | 优化查询、索引、隔离级别 |

| 外部等待 | 等待外部应用或网络 | OLEDB, BROKER<em>*, PREEMPTIVE</em>* | 检查外部依赖 |

TOP 10 常见等待类型速查

| 等待类型 | 含义 | 最常见原因 | 针对性排查 |

|———-|——|————|————|

| PAGEIOLATCH_SH | 等待从磁盘读页到内存 | 内存不足,或缺失索引导致大量物理读 | 检查缓冲池命中率,检查缺失索引 |

| WRITELOG | 等待日志写入磁盘 | 事务提交频繁,日志磁盘慢 | 批量提交,日志盘换SSD,检查磁盘队列 |

| LCK_M_X | 等待排他锁 | 长事务、锁升级、并发写冲突 | 查sys.dm_tran_locks,优化长事务 |

| SOS_SCHEDULER_YIELD | 自愿让出CPU(等待调度)| CPU压力大,查询未优化 | 检查CPU使用率,优化高代价查询 |

| ASYNC_NETWORK_IO | 等待客户端接收数据 | 客户端处理慢,网络慢 | 检查客户端应用,网络带宽 |

| PAGELATCH_EX | 等待页闩锁(内存结构)| tempdb竞争(PFS/GAM/SGAM页)| tempdb增加文件,使用分区表 |

| CXPACKET | 等待并行查询同步 | 并行查询的不均衡分布 | 调整MAXDOP,优化倾斜数据分布 |

| BACKUPTHREAD | 等待备份完成 | 备份任务占用资源 | 维护窗口做备份,调整备份压缩 |

| LOGBUFFER | 等待日志缓冲区空间 | 日志生成过快,磁盘跟不上 | 增大日志文件,优化大事务 |

| MEMORY_ALLOCATION_EXT | 等待内存分配 | 内存压力,大查询需要内存授予 | 调整max server memory,优化大排序/哈希 |

查询当前等待统计


-- 查看自上次重启以来的等待累积

SELECT wait_type, waiting_tasks_count, wait_time_ms,

&#x20;      max_wait_time_ms, signal_wait_time_ms,

&#x20;      (wait_time_ms - signal_wait_time_ms) AS resource_wait_ms,

&#x20;      CAST(wait_time_ms * 1.0 / SUM(wait_time_ms) OVER() AS DECIMAL(5,2)) AS pct

FROM sys.dm_os_wait_stats

WHERE wait_type NOT LIKE '%SLEEP%'  -- 过滤无意义等待

&#x20; AND wait_type NOT LIKE '%BROKER%'

&#x20; AND wait_type NOT LIKE '%QUEUE%'

&#x20; AND wait_time_ms > 0

ORDER BY wait_time_ms DESC;

现象解释

  • 为什么重启后性能短暂变好?sys.dm_os_wait_stats 被重置,但根本问题依然存在。

  • 为什么 CXPACKET 等待在并行度高的服务器上正常?少量 CXPACKET 正常,超过15%表示并行不平衡。

3. 系统化优化流程:5步法


步骤1:定位瓶颈(等待统计分析)

&#x20;   ↓

步骤2:关联到具体资源(CPU/内存/IO/网络)

&#x20;   ↓

步骤3:找到消耗资源最多的查询

&#x20;   ↓

步骤4:分析查询执行计划

&#x20;   ↓

步骤5:实施优化并验证效果

步骤1:定位瓶颈 —— 等待统计分析


-- 快速诊断当前瓶颈类型

SELECT 

&#x20;   CASE 

&#x20;       WHEN SUM(wait_time_ms) > 0 THEN 

&#x20;           (SELECT TOP 1 wait_type 

&#x20;            FROM sys.dm_os_wait_stats 

&#x20;            WHERE wait_type NOT LIKE '%SLEEP%' 

&#x20;              AND wait_type NOT LIKE '%BROKER%'

&#x20;            ORDER BY wait_time_ms DESC)

&#x20;       ELSE 'No significant waits'

&#x20;   END AS top_wait_type,

&#x20;   (SELECT SUM(wait_time_ms) FROM sys.dm_os_wait_stats 

&#x20;    WHERE wait_type NOT LIKE '%SLEEP%') AS total_wait_ms;

瓶颈分类决策树


TOP等待类型分析

├─ PAGEIOLATCH_* 主导 (>40%)

│   └─ 内存不足 或 I/O子系统慢 → 检查PLE、磁盘延迟

├─ WRITELOG 主导 (>30%)

│   └─ 日志写入瓶颈 → 检查事务大小、日志盘IOPS

├─ SOS_SCHEDULER_YIELD 主导 (>20%)

│   └─ CPU压力 → 检查CPU使用率、高查询

├─ LCK_M_* 主导 (>20%)

│   └─ 锁阻塞 → 检查孤岛事务、死锁

├─ LATCH_* 主导 (>15%)

│   └─ 内部竞争 → 检查tempdb、数据库页争用

└─ 无明显单一主导(分散)

&#x20;   └─ 混合负载正常,或整体压力大

步骤2:关联到具体资源指标

| 瓶颈类型 | 检查计数器 | 正常值 | 异常值 |

|———-|————|——–|——–|

| CPU | Processor Time, SQL Compilations/sec | 90% 持续 |

| 内存 | Page Life Expectancy, Buffer Cache Hit Ratio | >300秒, >95% | <60秒, <90% |

| 磁盘I/O | Avg Disk Sec/Read, Avg Disk Sec/Write | 50ms |

| 网络 | Network Interface Bytes/sec | 稳定 | 持续满载 |

| 锁阻塞 | Average Wait Time (ms) | 500ms |


-- 内存压力诊断

SELECT 

&#x20;   [name] AS counter_name,

&#x20;   CASE [counter_name]

&#x20;       WHEN 'Page life expectancy' THEN 

&#x20;           CASE WHEN cntr_value < 300 THEN '临界' WHEN cntr_value < 1000 THEN '警告' ELSE '正常' END

&#x20;       WHEN 'Buffer cache hit ratio' THEN 

&#x20;           CASE WHEN cntr_value < 90 THEN '临界' WHEN cntr_value < 95 THEN '警告' ELSE '正常' END

&#x20;   END AS status,

&#x20;   cntr_value

FROM sys.dm_os_performance_counters

WHERE [object_name] LIKE '%Buffer Manager%'

&#x20; AND counter_name IN ('Page life expectancy', 'Buffer cache hit ratio');

步骤3:找到消耗资源最多的查询


-- 当前正在运行的查询(按CPU/IO排序)

SELECT TOP 10 

&#x20;   r.session_id, r.cpu_time, r.total_elapsed_time,

&#x20;   r.logical_reads, r.writes, s.text AS query_text

FROM sys.dm_exec_requests r

CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) s

WHERE r.session_id > 50  -- 排除系统会话

ORDER BY r.cpu_time DESC;  -- 或 logical_reads, writes

-- 历史消耗统计(自上次重启)

SELECT TOP 10 

&#x20;   qs.total_worker_time / qs.execution_count AS avg_cpu,

&#x20;   qs.total_logical_reads / qs.execution_count AS avg_logical_reads,

&#x20;   qs.execution_count, qs.total_elapsed_time,

&#x20;   SUBSTRING(st.text, (qs.statement_start_offset/2)+1,

&#x20;       ((CASE qs.statement_end_offset

&#x20;           WHEN -1 THEN DATALENGTH(st.text)

&#x20;           ELSE qs.statement_end_offset

&#x20;       END - qs.statement_start_offset)/2) + 1) AS query_text

FROM sys.dm_exec_query_stats qs

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st

ORDER BY qs.total_worker_time DESC;  -- 或 logical_reads, elapsed_time

步骤4:分析执行计划

快速检查清单:

  • [ ] 是否有表扫描?预期行数大吗?

  • [ ] 估计行数 vs 实际行数差异 >10倍?

  • [ ] 是否有书签查找(Key Lookup)且占比高?

  • [ ] 是否有隐式转换(CONVERT_IMPLICIT)?

  • [ ] 连接方式合理吗(小表驱动大表)?

  • [ ] 是否有排序操作且无索引支持?

步骤5:实施优化并验证

优化类型优先级:


1. 查询重写(代价最低,效果可能最大)

&#x20;  - SARGable改造

&#x20;  - 消除隐式转换

&#x20;  - 拆分复杂查询

2. 索引优化(性价比最高)

&#x20;  - 添加覆盖索引

&#x20;  - 调整索引列顺序

&#x20;  - 删除无用索引

3. 配置调整(需谨慎)

&#x20;  - MAXDOP, Cost Threshold for Parallelism

&#x20;  - Max Server Memory

&#x20;  - 隔离级别(RCSI)

4. 硬件升级(最后手段)

&#x20;  - 增加内存

&#x20;  - 换成SSD

&#x20;  - 增加CPU核心

4. 关键性能计数器详解

CPU相关

| 计数器 | 意义 | 告警阈值 |

|——–|——|———-|

| Processor Time | CPU总体使用率 | >80% |

| SQL Compilations/sec | 编译次数(每秒) | >100 |

| SQL Recompilations/sec | 重编译次数 | >10 |

| Batch Requests/sec | 批请求数(吞吐量) | 趋势监控,突然下降可能问题 |

内存相关

| 计数器 | 意义 | 告警阈值 |

|——–|——|———-|

| Page Life Expectancy | 页在内存停留秒数 | <300秒 |

| Buffer Cache Hit Ratio | 缓冲池命中率 | <95% |

| Memory Grants Pending | 等待内存授予的查询数 | >0 |

| Target Server Memory vs Total Server Memory | 预期 vs 实际内存 | 实际 < 目标 持续 |

磁盘I/O相关

| 计数器 | 意义 | 告警阈值 |

|——–|——|———-|

| Avg Disk sec/Read | 平均读延迟 | >20ms(数据),>5ms(日志) |

| Avg Disk sec/Write | 平均写延迟 | >20ms(数据),>5ms(日志) |

| Disk Reads/sec | 每秒读次数 | 与硬件能力对比 |

| Disk Writes/sec | 每秒写次数 | 与硬件能力对比 |

监控脚本(快速健康检查)


-- 一键诊断(运行以上关键指标)

SELECT 'CPU Pressure' AS metric,

&#x20;   CASE WHEN cntr_value > 80 THEN 'HIGH' ELSE 'NORMAL' END AS status,

&#x20;   cntr_value AS value

FROM sys.dm_os_performance_counters

WHERE counter_name = 'Processor Time' AND instance_name = '_Total'

UNION ALL

SELECT 'Memory Pressure',

&#x20;   CASE WHEN cntr_value < 300 THEN 'HIGH' ELSE 'NORMAL' END,

&#x20;   cntr_value

FROM sys.dm_os_performance_counters

WHERE counter_name = 'Page life expectancy'

5. 常用DMV速查表

| 目的 | DMV/函数 |

|——|———-|

| 等待统计 | sys.dm_os_wait_stats |

| 当前请求 | sys.dm_exec_requests |

| 当前会话 | sys.dm_exec_sessions |

| 锁状态 | sys.dm_tran_locks |

| 查询统计(历史)| sys.dm_exec_query_stats |

| 查询计划 | sys.dm_exec_query_plan() |

| 索引使用 | sys.dm_db_index_usage_stats |

| 缺失索引 | sys.dm_db_missing_index_details |

| 计划缓存 | sys.dm_exec_cached_plans |

| 性能计数器 | sys.dm_os_performance_counters |

| 操作系统 | sys.dm_os_sys_info, sys.dm_os_windows_info |

6. 性能优化实战案例

案例1:突然变慢的报表查询

现象:每天早上8-10点报表慢,其他时间正常。

排查过程:

  1. 查等待统计:PAGEIOLATCH_SH 占70%

  2. 定位查询:大表扫描 + 书签查找

  3. 原因:统计信息每天凌晨更新,但查询模式改变

  4. 解决方案:添加覆盖索引,消除书签查找

  5. 验证:执行时间从2分钟降到3秒

案例2:偶发死锁

现象:每月几次死锁错误(1205),重试后成功。

排查过程:

  1. 启用TF1222捕获死锁图

  2. 分析死锁图:两个事务交叉更新同一表的不同行

  3. 原因:非聚集索引书签查找导致锁顺序不一致

  4. 解决方案:统一更新顺序(ORDER BY),添加覆盖索引

  5. 验证:监控一个月,死锁消失

案例3:CPU持续99%

现象:CPU长时间跑满,响应变慢。

排查过程:

  1. 查等待统计:SOS_SCHEDULER_YIELD 占60%

  2. 找高CPU查询:多个查询存在隐式转换

  3. 原因:WHERE VARCHAR列 = 数字,强制全表扫描

  4. 解决方案:统一参数类型,修改索引

  5. 验证:CPU降至40%,业务恢复正常

7. 性能优化的常见陷阱

| 陷阱 | 错误做法 | 正确做法 |

|——|———-|———-|

| 过早优化 | 随便加索引,不管写入影响 | 先分析瓶颈,确认收益>成本 |

| 只看平均 | 平均延迟正常就认为没问题 | 关注P95、P99峰值 |

| 忽视基线 | 不知道正常时什么样 | 建立性能基线,对比异常 |

| 一次改太多 | 同时改索引、查询、配置 | 逐一改动,验证效果 |

| 生产直调 | 直接在生产调参 | 仿真环境验证完整流程 |

| 只看SQL Server | 忽略OS、网络、存储 | 全链路排查 |

8. 建立性能基线

基线内容

  • 业务负载特征:每秒批请求数、并发会话数

  • 等待类型分布:TOP5等待占比

  • 资源使用:CPU、内存、磁盘I/O平均值和峰值

  • 关键查询:TOP10 CPU/IO查询的平均执行时间

  • 业务指标:核心事务响应时间的P50/P95/P99

工具推荐

  • 内置报表:SQL Server 标准报表(右键数据库 → 报表)

  • Performance Dashboard:SQL Server 官方DMV仪表盘

  • sp_Blitz:Brent Ozar 开源诊断工具

  • sp_WhoIsActive:实时监控当前活动(Adam Machanic)


-- 安装 sp_WhoIsActive(推荐)

-- 下载地址:http://whoisactive.com

EXEC sp_WhoIsActive @get_plans = 1, @get_locks = 1;

9. 优化前中后的检查清单

优化前

  • [ ] 收集当前性能指标(等待统计、计数器、基线)

  • [ ] 确认问题范围(全局/特定库/特定查询)

  • [ ] 找到最可能的瓶颈类型

  • [ ] 设计优化方案(含回滚计划)

  • [ ] 评估优化风险(锁、停机时间)

优化中

  • [ ] 在非生产环境验证

  • [ ] 监控优化过程中的关键指标

  • [ ] 记录改动前后对比

  • [ ] 准备回滚脚本

优化后

  • [ ] 验证业务指标改善

  • [ ] 监控副作用(其他查询是否变慢)

  • [ ] 更新文档和基线

  • [ ] 通知团队优化结果


第十期小结

性能优化不是玄学,而是一套可复用的方法论:等待统计定位瓶颈 → 资源指标确认 → 找到消耗查询 → 分析执行计划 → 实施验证。关键工具是sys.dm_os_wait_statssys.dm_exec_query_stats和性能计数器。建立基线和遵循优化流程,能让你从“碰运气”变成“有据可依”。记住:性能优化的第一原则是度量,而不是猜测


整个系列总结

这十期内容覆盖了SQL Server基础架构的核心知识:

| 期数 | 主题 | 核心要点 |

|——|——|———-|

| 第一期 | 整体架构 | 实例、数据库、文件组、执行引擎vs存储引擎 |

| 第二期 | 缓冲池与内存 | Page Life Expectancy、Checkpoint、LazyWriter |

| 第三期 | 事务日志与WAL | VLF、WAL、崩溃恢复(分析+重做+撤消) |

| 第四期 | 索引物理存储 | 堆、聚集/非聚集索引、覆盖索引、书签查找 |

| 第五期 | 并发控制(上) | 隔离级别、锁类型、锁升级、阻塞分析 |

| 第六期 | 并发控制(下) | 死锁检测、死锁分析、消除策略 |

| 第七期 | 查询优化器 | 执行计划、统计信息、参数嗅探 |

| 第八期 | 事务内部实现 | LSN、行版本控制、ARIES恢复 |

| 第九期 | 高可用与DR | 日志传送、复制、Always On AG |

| 第十期 | 性能调优方法论 | 等待统计、DMV分析、系统化流程 |

这十期构成了SQL Server基础架构的知识骨架,帮助你理解现象本质、定位问题根源、制定优化方案。后续可深入学习的方向包括:查询优化器高级话题、分区表与列存储、内存优化表、Azure SQL数据库架构等。


绩隐金 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:第十期:性能监控与调优方法论 —— 从等待统计到DMV分析
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址