简介:一份面向 SQL Server 管理与开发人员的 Profiler 工具中文文档,系统讲解 SqlServer2000 性能工具的开启、跟踪创建与事件分析流程。文档从企业管理器打开 Profiler、新建跟踪、设置事件与数据列、过滤条件讲起,并介绍跟踪模板的复用方法;同时针对 Profiler trace 文件性能分析的传统局限,引入 Read80trace 的 Normalization 功能和 usp_GetAccessPattern 存储过程分析技巧,可用于定位 HOT 数据库、优化慢查询与死锁问题。资源为单个 doc 文档,压缩包约 308KB,内容紧凑且配有操作界面说明与目录索引,适合刚接触 Profiler 的 DBA 或需要系统梳理性能优化思路的学习者。目前已有 597 人学习下载,对快速上手 SQL Server 监控与排障具有实用参考价值。
1. 老库变慢,你还得靠 SqlServer2000 Profiler
搜到这份标题的人,多半不是来考古的,是手头真有一台 SQL Server 2000 还在跑关键业务:月底报表慢、应用报 1205 死锁错误、某条查询偶发卡死。这种排查没有捷径,服务器不会直接告诉你哪条 SQL 是元凶,只能把自带的性能工具 Profiler 打开,把真实流量录下来。它不做分析、不给索引建议,只按你设定的事件和筛选条件,记录服务器上实际发生的 T-SQL 批处理、锁事件、连接和性能指标。这篇把建跟踪、定筛选、读结果、避坑的完整流程讲清楚,适合还在维护老库的 DBA,以及准备做 SQL Server 2000 升级评估的人。
2. SqlServer2000 Profiler 是靠什么抓出慢 SQL 的
在动手点界面之前,先花几分钟理解 Profiler 的数据通路。这一步不做,很容易出现开了跟踪之后文件巨大、却找不到一条有用数据的尴尬场面。Profiler 本质上不是"录屏工具",它更像一个按事件触发的采集器:服务器在读 SQL、锁资源、编译批处理时抛出事件,Profiler 收到后把事件和你勾选的数据列拼成一行记录。所以,选了什么事件、勾了什么列、设了什么筛选,直接决定你最后看到的是金矿还是垃圾。
2.1 事件驱动跟踪:先理解事件、数据列、筛选这三件事
Profiler 是 SQL 服务端跟踪机制(SQL Trace)的图形前端,跟踪一旦建立,即使 Profiler 窗口被挡住、最小化,服务器端仍在按配置工作。这套机制里最重要的就是三个概念。
第一是事件类别,它定义"记什么":一个事件代表服务器发生的某一类动作,比如 SQL:BatchCompleted 表示一个 T-SQL 批处理执行完成,Lock:Deadlock 表示检测到一次死锁。第二是数据列,它定义"每条记录附带哪些属性":比如 CPU、Reads、Writes、Duration、TextData,这些列拼在一起才是一条完整记录。第三是筛选,它定义"哪些记录值得留下来":筛选是采集阶段就生效的,不满足条件的记录根本不会被收集,而不是收下来再让你事后过滤。很多新手把筛选当成事后整理工具,结果一开跟踪就把生产环境拖慢,这是对这套机制最大的误解。
理解这一点之后,就知道为什么排查慢查询不能靠查询分析器手动执行语句。查询分析器只能测你自己写的命令,看不到业务程序里真实发出的 SQL;性能监视器能给汇总指标,但看不到具体是哪条语句消耗了 CPU。Profiler 正好补上这段空白:把服务器当成黑匣子,把所有经过它的事件按你指定的口径暴露出来。
2.2 主要的几个事件类:慢查询和死锁各盯哪些
新手第一次打开事件选择页会吓一跳,里面几十个事件类分门别类摆在那里,其实只会干扰判断。我平时排查时基本只看这几个。
| 事件类 | 触发时机 | 主要用途 |
|---|---|---|
| SQL:BatchStarting | 收到完整 T-SQL 批处理,开始编译执行 | 看客户端发了什么语句;配合阻塞判断谁启动了一直不结束 |
| SQL:BatchCompleted | 批处理执行完成 | 统计慢查询核心:Duration、CPU、Reads 全在这一行 |
| RPC:Completed | 存储过程或远程过程调用完成 | 定位存储过程中的慢步骤,能看到整体耗时 |
| Lock:Deadlock | 检测到死锁 | 配合死锁信息看参与对象的锁关系 |
| Lock:Deadlock Chain | 发生级联死锁 | 多进程连环死锁时看完整链条 |
| Attention | 客户端取消请求或连接超时 | 业务端放弃等待时的事件,常和阻塞一起出现 |
抓慢查询的最小组合是 SQL:BatchCompleted 加 RPC:Completed,前者覆盖即席 SQL,后者覆盖存储过程。排查死锁要单独开一个跟踪模板,把 Lock:Deadlock 勾上,而不是把它和慢查询事件混在一起。混在一起的问题在于慢查询事件量大,死锁记录会被淹没在几千行滚动记录里,后面看图的时候手都酸。
2.3 数据列怎么选:五个核心字段的分析顺序
数据列的选择要克制。默认模板勾了很多列,看起来全面,读起来费劲。我常用的核心字段就五个:TextData、CPU、Reads、Writes、Duration,外加一个 StartTime 用来对齐时间点。
这里有个血泪经验:Duration 大不代表语句本身慢。Duration 是这条语句从开始到返回的端到端时间,中间包括拿锁等待、磁盘 IO、网络回传,甚至包括它排队等 CPU 的时间。而 CPU 是真正处理这条语句消耗的处理器时间。所以第一次看结果,不要只按 Duration 排序就下结论,要横着看一组:CPU 高且 Duration 高,说明语句真在干活,多半是索引缺失或隐式转换;CPU 低但 Duration 高,说明语句大部分时间在等锁或等 IO,这时候改 SQL 文本基本没用,要查阻塞和磁盘瓶颈。
Reads 代表逻辑读次数,数字大通常是表扫描或者大范围读的信号。同一语句第一次跑和缓存热了之后差距很大,所以要看平均而不是单次。TextData 是语句文本,长度一大只会截断一屏,后面我会说怎么在文件里取完整值。
2.4 开启跟踪前先回答的三个问题
在点 Run 之前先自己问三句:这次要解决什么问题?问题出现在什么时间窗口?跟踪结果放哪里?
要解决的问题决定了事件类组合,是慢查询、死锁、还是连接数异常。时间窗口决定了跟踪跑多久,一般建议比问题时间窗前后各多 10 分钟,比如业务说上午 10 点到 10 点半出问题,就从 9 点 50 分抓到 10 点 40 分。结果放哪里决定了保存方式,放文件开销小、后续可重放,放表方便用 SQL 做统计聚合。这三个问题回答完,再进第 3 章的界面操作,就不会瞎配一通跑出来一堆没用的数据。
3. 建立第一个跟踪:从启动到看到慢查询
这一章按实际界面路径走一遍。以 SQL Server 2000 自带的 Profiler 为准,不同补丁版本菜单差异不大,位置基本固定。
3.1 启动与新建跟踪:最基础的四步
启动 Profiler 有两个入口:开始菜单程序组里的 Microsoft SQL Server -> Profiler,或者先开企业管理器,在 Tools 菜单下选 SQL Server Profiler。两个入口连的都是同一个工具。
新建跟踪的路径按下面走:
- 打开 Profiler 后,菜单 File -> New Trace。
- 在弹出的连接窗口里选择要监控的 SQL Server 实例,填好登录名和密码。这里只有 sysadmin 固定服务器角色成员能成功创建跟踪,普通账号连上去会直接报权限不足。
- 连接成功后出现跟踪属性窗口,在 General 页签给跟踪起名,比如 Probe_SlowQuery。
- 依次在 Events、Data Columns、Filters 三个页签里配置完,点 Run 开始。
注意一点:Profiler 连接实例成功不代表跟踪就建好了,要看到界面里开始滚动数据、状态显示 Running,才算真正在采集。以前有人在这一步栽过跟头,起了名没点 Run,窗口空着挂一下午,回来自以为抓到了数据,结果什么都没有。
3.2 三个必调参数:Duration、ApplicationName、DatabaseID
Filters 页签是最关键的一页。没有筛选的 Profiler 跑一分钟就能产生上万行记录,筛选设得好,留下的每一行都是问题相关数据。我每次必调的就是下面三个参数。
| 数据列 | 常用设置 | 作用 |
|---|---|---|
| Duration | 大于等于 1000 或 3000 | 只留下耗时超过阈值的语句,数值单位是毫秒 |
| ApplicationName | 不等于某固定值 | 排除维护作业和管理工具产生的干扰语句 |
| DatabaseID | 等于业务库对应的 ID | 只抓目标库,避免其他库的语句混进来 |
Duration 是筛选里最常用的一项。抓慢查询时我习惯先设大于等于 1000 毫秒,如果结果还是太多就调到 3000 或 5000,抓取结束后如果再想看更慢的,直接在导入后的跟踪表里加大阈值重新查,不需要重新跑一次跟踪。ApplicationName 用来过滤。每条到服务器的连接都带有应用程序名称,查询分析器、备份作业、监控脚本各有各的 AppName。如果不排除它们,抓完的结果里一半是维护脚本的探测语句,看着碍眼。DatabaseID 的写法要注意,Profiler 的筛选条件是数据列级别,数据库筛选最好用 ID 而不是名称,因为名称匹配在部分事件上不生效。先连到目标实例,执行下面这条语句查出业务库的 ID:
SELECT name, dbid FROM master.dbo.sysdatabases ORDER BY dbid查到数字后填进筛选条件。老实例上的业务库数量不多,这一步一分钟就能完成。筛选条件里运算符是"大于等于""小于等于"这一类,不是直接写等于。填错了方向问题很隐蔽,我在第 4 章里专门讲这个翻车点。
3.3 保存策略:文件滚动与建表分析
每次抓取之前都要先想好结果怎么存。Profiler 支持直接保存到文件,也支持抓完导入表,我一般分两步走:抓取时保存为二进制跟踪文件,分析时再导入表。
保存文件的好处是开销低、能重放。在跟踪属性窗口勾选 Save to file,指定文件路径后,设置最大文件大小,比如 200MB,并勾上 Enable file rollover。这样文件写满会自动切换到下一个文件,避免长时间抓取时日志大小把磁盘撑爆。如果没有勾选滚动,文件写满后跟踪会自动停止,这一小时的数据就白抓了。
抓完分析时,从 Profiler 菜单 File -> Save As -> Trace table,把跟踪文件导入到一张指定表里,之后就能用 SQL 对结果做统计。这是 Profiler 排障最有价值的一步,所有文本过滤、排序、聚合都变得很轻松,比在 Profiler 界面里翻滚动记录高效得多。导入后的表里 EventClass 是数值编码,TextData 是长文本类型,后续查询时按文本截断再聚合就好。
3.4 运行时的界面怎么读,别盯着滚动发呆
跟踪运行起来以后,界面上半部分会不断滚动事件行,下半部分显示当前选中行的各字段明细。这时候有一点要提醒:不要一直盯着滚动窗口等结果,长时间的高频率事件会把 Profiler 客户端拖慢,服务器端的采集反而正常。真需要长时间观察时,窗口最小化或者干脆让它写到文件里,隔一段时间再回来看。
界面里两个关键信号值得留意。一是同一 SPID 反复出现,且每次都是同一类事件,说明某个连接在不断提交语句,可能是循环,也可能是应用层重试。二是同一个 SPID 只有 BatchStarting 状态,长时间等不到 BatchCompleted,配合查询分析器里的 sp_who2 看阻塞头,基本能定位到是谁堵住了谁。运行过程不需要额外干预,让跟踪按配置跑完预设窗口就行。
4. 避坑:用 Profiler 排查时最容易翻车的 5 个场景
Profiler 用多了会踩不少坑,每个坑都曾经让排查白费半天时间。这章按"现象、原因、解决"三步把这些场景写清楚,都是实际会遇到的。
4.1 跟踪开着,生产库负载反而上去了
现象:跟踪开启后,服务器 CPU 上涨明显,原本能扛住的业务开始排队,DBA 被业务方投诉。
原因:事件类勾选太多、没有筛选,服务器每分钟抛几万条记录给 Profiler 客户端,而客户端还要实时刷新到界面。跟踪本身有开销,事件量越大开销越明显,老机器上尤其突出。
解决:先停止跟踪,把事件类精简到只留要用的那一类,Filters 里把 Duration 阈值调高;如果只是定位问题不是做全量审计,没有必要勾所有事件。另外,长时间抓取优先保存到文件而不是盯着界面看,客户端渲染开销能省一大截。这条属于"性能工具自己的性能也是性能",不能忽略。
4.2 跟踪异常退出后,服务器端还有个看不见的跟踪在跑
现象:Profiler 窗口被强杀或者机器重启,隔一会儿发现之前的跟踪文件还在增大,甚至业务还在受影响。
原因:Profiler 只是跟踪的客户端,服务器端建立跟踪以后,是独立的服务端对象。客户端异常退出,不会自动清理服务器端的跟踪定义,形成孤儿跟踪。
解决:连上实例执行下面的查询,找到还在运行的跟踪,然后用扩展存储过程停止并删除它:
SELECT * FROM master..systraces WHERE status <> 0查到要清理的 traceid 后,依次执行停止和删除:
EXEC sp_trace_setstatus @traceid = 1, @status = 0 EXEC sp_trace_setstatus @traceid = 1, @status = 2这里的 traceid 要换成上一步查出来的真实值。第一条命令是停止跟踪,第二条命令是关闭并删除跟踪定义,顺序不能反。这个坑不常见,但碰上就很麻烦,因为没人知道它还在偷偷消耗服务器资源。
4.3 Duration 排序第一名的语句,不一定是元凶
现象:按 Duration 降序排列结果,排第一的是一条很简单的 UPDATE,开发一口咬定这条语句不可能慢。
原因:Duration 是端到端耗时,包含等待、排队、锁阻塞时间。一条 UPDATE 本身工作量很小,但如果它要更新的行被别人锁住,它会一直等到锁释放才执行,等待时间全计在 Duration 里。
解决:不要只看 Duration,横着看 CPU 和 Reads。CPU 接近 0、Reads 很小、Duration 却很大,基本可以判断语句是在等锁,而不是自己慢。下一步去查同时段的阻塞链,查询分析器执行 sp_who2,找阻塞和被阻塞会话,再顺着 ApplicationName 和 HostName 找到对应的业务程序。等锁场景下改 SQL 文本没有意义,要解决的是锁的获取顺序和事务持有时间。
4.4 死锁图看到了,但不知道谁抢了谁
现象:应用报 1205 死锁错误,打开 Profiler 里的死锁图形,两个进程图标和资源图标缠在一起,看不出到底谁在等谁。
原因:死锁图形是给有一定锁机制基础的人读的,新手不知道 owner 和 waiter 的箭头方向,自然看不懂。
解决:先把死锁图里两个进程对应的事件 ID 记下来,再看资源对象名,确认是表还是索引键值。通常死锁就两种情况:两个事务以不同顺序更新多张表,或者同时申请同一范围间隙锁。知道资源对象后,把两条 SQL 的访问表顺序列出来对比,如果都是 A、B、C 的顺序,锁就会被串行释放,死锁不会出现。解决手段是统一所有事务的访问顺序,或者缩小事务里更新语句的范围。图形本身不会告诉你哪条 SQL 该改,但它会告诉你锁资源在哪,顺着资源去找代码就对了。
4.5 重放功能用错了,测试库被真实删改搞乱
现象:拿到生产的跟踪文件,想在测试环境"重放一次"复现问题,结果重放把测试库里的数据给删掉了。
原因:跟踪文件里保留的不只是 SELECT,还有生产环境真实发生的 INSERT、UPDATE、DELETE。Profiler 重放时会按事件顺序原样执行这些命令,测试库就成了被真实业务操作轰炸的靶场。
解决:重放之前先明确跟踪文件里有没有写操作。如果只想复现性能问题,先把跟踪文件筛选一遍,剔除删除更新类语句,只留下查询类事件;或者在重放选项里限定只重放某个 ApplicationName 的连接会话。就算这样,重放前也一定要给测试库做完整备份。重放功能不是后悔药,它是复制现场的工具,用之前得先把能破坏数据的地方堵住。
5. 用 Profiler 定位一次"数据库突然变慢"的完整流程
把前面的概念和操作串起来,这里完整走一遍排查过程。假设接到一个典型求助:每天上午 10 点到 10 点半,某业务库响应变慢,其他时段正常。
5.1 动手前先确认三件事
不要上来就开 Profiler,先花五分钟把情况问清楚。第一,慢的具体表现是什么,是报表查询慢还是写操作阻塞,有没有 1205 错误出现;第二,问题窗口期是每天固定还是某几天;第三,慢的时候有没有人正在跑批量任务,比如数据导入、索引重建、历史数据归档。这三个问题的答案直接决定跟踪模板怎么建。
同时检查 Profiler 能以什么身份连接目标实例,确认当前 Windows 登录账号或 SQL 登录账号有这个实例的系统管理员权限。再确认落盘路径的剩余空间。跟踪文件在数据量大时增长速度惊人,至少预留 2GB 空间,路径所在的盘也不能是系统盘,避免把系统盘写满导致服务器出问题。
5.2 建一个慢查询加阻塞的组合跟踪
根据上面的信息,我一般会建一个组合模板,事件类选 SQL:BatchCompleted、RPC:Completed、Lock:Deadlock、Lock:Deadlock Chain 四个。同时把 SQL:BatchStarting 也勾上,它配合 BatchCompleted 使用,能够识别"启动了但没完成"的语句,这是找阻塞链条的关键。
Filters 设置上,Duration 大于等于 1000 毫秒,DatabaseID 填业务库编号。不勾选太多列,保留 EventClass、TextData、SPID、DatabaseID、CPU、Reads、Writes、Duration、StartTime、ApplicationName。StartTime 必须有,因为问题有固定窗口期,后面聚合要按时间分桶看趋势。保存方式选择文件,勾上滚动,最大文件大小设 200MB。设置完成后点 Run,让跟踪跨过整个问题窗口,从 9 点 50 分抓到 10 点 40 分。
5.3 抓取结束后把结果导入表并按耗时排序
停止跟踪后,把跟踪文件另存为跟踪表。导入完成后运行下面这条查询,先看前 50 条最耗时的记录:
SELECT TOP 50 SPID, ISNULL(CONVERT(VARCHAR(4000), TextData), '') AS [SQL], CPU, Reads, Writes, Duration, StartTime FROM TraceResult_20240115 WHERE Duration >= 1000 ORDER BY Duration DESC注意这里 TextData 是文本类型,直接显示会带出不可见字符,所以先转成 VARCHAR 再取。Duration 单位是毫秒,1000 表示 1 秒。如果查询结果里出现大量 SPID 相同、SQL 文本相似、Duration 递增的记录,就说明那段语句在循环或者被反复重试,是典型的循环批次。如果结果里 Duration 大的记录 CPU 都很小,按第 4 章第 3 条分析,重点转向锁等待。如果看到某条 SELECT 的 Reads 达到几万,而它只是简单条件查询,基本可以锁定缺索引。
5.4 顺着结果找根因的三个信号
处理结果时我习惯按三个信号分头排查。信号一是同一段文本反复出现几百次,说明应用层在拿同一条 SQL 反复执行,要考虑是不是没有参数化导致每次执行计划都重新编译,重点看 CPU 总量。信号二是高 Duration 高 CPU 高 Reads 三者同时出现,这类语句通常是大表扫描或隐式转换,先把 WHERE 条件字段的隐式转换查掉,再看索引。信号三是高 Duration 但 CPU 和 Reads 都很低,这时去查数据库的等待状态,锁等待常见症状,也和长时间事务没提交有关,重点找阻塞源头。
还有一种常见情况:跟踪结果里 TextData 很短,只有类似 "exec proc_report" 这样的调用,看不清楚存储过程内部哪一段慢。这种就把存储过程文本拉出来,手工执行拆解,或者等下次跟踪时同时按事件类勾上 Stored Procedures 系列事件,看过程内部各步骤的耗时分布。SQL Server 2000 的 Profiler 支持存储过程相关事件明细,打开后能区分 StepStarting 和 StatementCompleted,定位内部最近一条耗时语句。
6. 让跟踪结果能复现:模板保存与基线对比
Profiler 用得顺手之后,有两件事一定要养成习惯:把调好的跟踪保存成模板,以及用两次跟踪做基线对比。
模板保存很简单,跟踪配置完成后在属性窗口里选保存模板,命名成容易识别的名字,比如 Probe_Slow_Query。以后再接任何新实例或者做周期性排查,New Trace 时直接选这个模板,五分钟之内就能开抓,不用每次把事件类、数据列、筛选重新配一遍。它保证的不只是省时间,更保证每次抓取口径一致,口径一致才有对比价值。
基线对比是验证优化是否有效的关键动作。做法是:优化前抓 30 分钟的结果导入表 A,优化后再在相同业务时段抓 30 分钟导入表 B,两张表结构相同。然后用相同文本前缀做关联,对比同一条语句优化前后的耗时和读次数。下面这条查询按 SQL 文本前 80 个字符做分组,对比前后两段窗口的平均耗时:
SELECT LEFT(CONVERT(VARCHAR(200), a.TextData), 80) AS SQL_Key, COUNT(*) AS Cnt, AVG(CONVERT(BIGINT, a.Duration)) AS AvgDur_Before, AVG(CONVERT(BIGINT, b.Duration)) AS AvgDur_After, AVG(b.Reads) AS AvgReads_After FROM TraceResult_Before a LEFT JOIN TraceResult_After b ON LEFT(CONVERT(VARCHAR(200), a.TextData), 80) = LEFT(CONVERT(VARCHAR(200), b.TextData), 80) GROUP BY LEFT(CONVERT(VARCHAR(200), a.TextData), 80) ORDER BY AvgDur_Before DESC这段 SQL 的作用是:以语句文本前 80 个字符作为键,把优化前后两次采集结果关联起来,算出同一条语句优化前后的平均耗时差异。窗口对齐很重要,两次抓取最好选相同时间段,比如都是工作日上午 10 点到 10 点半,才能排除业务高峰造成的干扰。平均耗时有明显下降、Reads 明显减少,才能确认索引或改写生效;如果只是个别语句改善,整体波动没变,说明优化没有踩到真正的根因。
我当年刚用 Profiler 时,把 Duration 大当成慢的唯一指标,对着一条等锁的 UPDATE 反复改写了一天,一点效果没有。后来养成横看 CPU 和 Reads 的习惯,又加上基线对比,才明白工具给的是交叉印证的数据,不是自动结论。排查性能问题,手边有 Profiler 这份底牌,至少不会在服务器面前瞎猜。希望帮到你。
本文还有配套的精品资源,点击获取