ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

免费SQL Server Profiler替代工具源码与C#财务管理系统实战

免费SQL Server Profiler替代工具源码与C#财务管理系统实战 简介一套完整的C#财务管理系统源码围绕 SQL Server Profiler 性能优化实践展开适合有一定 C# 基础、希望接触真实业务场景的开发者和在校学生。项目覆盖账务处理、报表生成、固定资产、成本核算、预算控制与用户权限等财务核心模块代码中可学习 ADO.NET 数据交互、Entity Framework 对象关系映射、Windows Forms 界面事件处理、多线程并发及异常处理等关键技能。压缩包共116个文件其中59个 C# 源文件是主线另含17个引用程序集、11个资源文件以及图片、配置和工程文件整体大小约3.01MB目录划分清晰便于按模块逐个阅读和调试。已有84人加入学习。参考这套源码既能理清分层架构与模块设计思路也能结合 SQL Server Profiler 监控语句性能、定位索引缺失或锁等待问题从而把 C# 开发与数据库优化真正落地。1. 为什么我放弃了 SQL Server Profiler一套可改源码的替代工具SQL Server Profiler 在持续跑跟踪时对生产库的负载不小抓完的 trc 文件越大图形界面里翻越卡微软官方又把它标记为弃用功能很多 DBA 转向扩展事件但 XEL 文件解析起来又是一套新东西。这份资源包里有两样东西一个免费的 SQL Server Profiler 替代工具源码和一套 C# 财务管理系统完整源码。前者适合排查慢 SQL、做性能基线后者适合学习 C# 三层架构、复式记账和报表实现。不管你是 C# 新手想拿一套真实项目练手还是已经有几年经验的开发想找一款能改源码的 SQL 跟踪工具这套资源都值得花时间过一遍。重点在于两个资源都是源码形态意味着你可以按自己的业务场景改过滤条件、改界面、改存储逻辑而不是用别人写死的黑匣子。2. 免费 SQL Server Profiler 替代工具跟踪原理、配置与落库2.1 服务端跟踪的取舍为什么不用 Profiler 原生界面SQL Server Profiler 有两种工作模式客户端跟踪界面直接抓和服务端跟踪定义 sp_trace_create 存储过程由 SQL Server 后台写 trc 文件。免费替代工具普遍走服务端跟踪——定义一个跟踪事件集合让 SQL Server 自己把结果写到文件工具只在需要分析时读取 trc 文件。这样做的第一个好处是负载低客户端跟踪每一条事件都要回传 UI网络和 CPU 都有开销服务端跟踪写文件几乎是顺序 IO负载可控。第二个好处是可离线分析trc 文件拷到任何一台机器上都能解析。这套 free-sql-server-profiler 源码的核心逻辑不复杂先读配置文件里的连接字符串连上 SQL Server 后执行一组系统存储过程创建跟踪然后挂在后台轮询 trc 文件的增长把新行解析成 DataTable 显示在表格里。它解决的痛点不是“抓不到”而是“Profiler 太重、扩展事件太难解析”——它在中间给了一个轻量入口。从源码里你能看到完整的事件列映射哪些列对应哪些字段、哪些事件类型带时长全是硬编码在解析层里的。扩展事件目前是微软官方推荐的方向但 XEL 文件解析需要自己写读取器开源方案不多。SQL Trace 虽然被标记为弃用在 SQL Server 2019 上依然可用中小系统用服务端跟踪做性能诊断完全够。如果数据库是云端托管实例比如 Azure SQL Database服务端跟踪不可用那只能走扩展事件。这套源码的适用边界就在本地部署或云虚拟机上的 SQL Server脑子里带着这个边界去用就不会白折腾。2.2 配置文件与连接参数最小可跑通的配置先看最小配置。源码里一般会带一个 App.config 或 appsettings.json核心是 SQL Server 连接字符串和跟踪过滤条件。connectionStrings add nameSqlTraceConnection connectionStringServer127.0.0.1,1433;Databasemaster;User Idtrace_user;PasswordYourPass;TrustServerCertificateTrue; providerNameSystem.Data.SqlClient / /connectionStrings appSettings !-- 跟踪文件目录工具会在启动时自动创建 -- add keyTraceFileDir valueC:\TraceFiles / !-- 单个trc文件大小上限单位MB超过自动滚动 -- add keyTraceFileMaxSize value200 / !-- 过滤条件只捕获执行时长超过阈值的语句单位毫秒 -- add keyMinDurationMs value1000 / !-- 只跟踪指定数据库为空表示所有数据库 -- add keyDatabaseFilter valueFinanceDB / /appSettings逻辑说明SqlTraceConnection 指向 master 库因为创建跟踪的系统存储过程需要在服务器级上下文执行trace_user 需要有 ALTER TRACE 权限直接塞 sa 风险大。TraceFileDir 是 trc 文件的落地目录SQL Server 服务账户要有写权限。MinDurationMs 是关键过滤参数设成 1000 表示只记执行超过 1 秒的语句日常监控我一般设 500排查慢 SQL 时再临时调成 100。DatabaseFilter 按库过滤多库环境建议必填否则 trc 文件涨得飞快。参数说明里最容易被忽略的是 TraceFileMaxSize。200MB 是保守值如果监控时间超过一天建议拆成 100MB方便分批归档。真机上常见做法是改完配置先跑一条主键查询做冒烟测试确认 trc 目录出现文件后再回到 Profiler 界面比对事件数是否一致。TrustServerCertificateTrue 在 SQL Server 2019 之后很关键老源码的连接串里没有这个会导致 TLS 握手阶段直接失败。2.3 把 trc 文件解析成 DataTable核心读取代码工具的核心是解析 trc 文件。常见做法是调用 fn_trace_gettable 函数这个表值函数可以直接把 trc 文件按表返回不需要手工解析二进制格式。public DataTable ReadTraceFile(string trcPath) { string query SELECT EventClass, TextData, CPU, Duration, Reads, Writes, StartTime, SPID, DatabaseID, ObjectName, LoginName FROM fn_trace_gettable(path, default); -- 第一个参数是trc完整路径第二个参数是读取的rollover文件数 -- 设为-1表示读取默认数量的滚动文件; DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(_connStr)) using (SqlCommand cmd new SqlCommand(query, conn)) { cmd.Parameters.AddWithValue(path, trcPath); cmd.Parameters.AddWithValue(default, -1); conn.Open(); using (SqlDataAdapter da new SqlDataAdapter(cmd)) { da.Fill(dt); } } return dt; }逻辑说明fn_trace_gettable 只接受路径和文件数两个参数路径必须带 .trc 后缀文件数 -1 代表按跟踪定义的滚动数量全部读取。EventClass 是事件类型编号RPC:Completed 是 10SQL:BatchCompleted 是 12一般只关心这两种 Completed 事件因为 Completed 事件里带执行时长和 CPU 消耗Starting 事件不带抓了只有干扰。参数说明Duration 单位是微秒不是毫秒很多人在这块踩过坑除以 1000 才是毫秒。CPU 同样以微秒计。DatabaseID 是 int需要 left join sys.databases 才能转成库名。Reads 是逻辑读次数这个值高往往比 Duration 更能说明索引问题——一个查询跑 1 秒但 Reads 是 100 万基本可以断定全表扫描或索引缺失。拿到 DataTable 后界面层直接绑定 DataGridView再按 Duration 排个序一个最简慢 SQL 分析器就出来了。3. C# 财务管理系统源码数据库设计与三层实现3.1 财务核心表设计科目、凭证、分录、余额财务系统看起来功能多剥开核心就是一套“凭证驱动”的模型。科目表存会计科目凭证表记每一笔业务的头分录表存借贷明细余额表或试算平衡表是统计结果。这套源码里的表结构大致是这样。-- 会计科目表 CREATE TABLE T_Account ( AccountCode VARCHAR(20) NOT NULL, -- 科目编码如1001 AccountName NVARCHAR(50) NOT NULL, -- 科目名称如库存现金 AccountType TINYINT NOT NULL, -- 1资产 2负债 3权益 4成本 5损益 IsLeaf BIT NOT NULL DEFAULT 1, -- 是否叶子科目1表示可记明细账 Status TINYINT NOT NULL DEFAULT 1, -- 1启用 0停用 CONSTRAINT PK_Account PRIMARY KEY (AccountCode) ); -- 凭证表 CREATE TABLE T_Voucher ( VoucherID INT IDENTITY(1,1) PRIMARY KEY, VoucherDate DATETIME NOT NULL, -- 制单日期 VoucherNo VARCHAR(30) NOT NULL, -- 凭证字号如记-202501-001 AttachCount INT DEFAULT 0, -- 附件张数 TotalDebit DECIMAL(18,2) NOT NULL, -- 借方合计冗余字段核对用 TotalCredit DECIMAL(18,2) NOT NULL, -- 贷方合计 Maker NVARCHAR(50), -- 制单人 AuditStatus TINYINT DEFAULT 0, -- 0草稿 1已审核 2已过账 CreateTime DATETIME DEFAULT GETDATE() ); -- 凭证分录表 CREATE TABLE T_VoucherEntry ( EntryID INT IDENTITY(1,1) PRIMARY KEY, VoucherID INT NOT NULL, LineNo TINYINT NOT NULL, -- 行号每张凭证从1开始 AccountCode VARCHAR(20) NOT NULL, Summary NVARCHAR(100), -- 摘要如支付办公用品费用 DebitAmt DECIMAL(18,2) DEFAULT 0, CreditAmt DECIMAL(18,2) DEFAULT 0, CONSTRAINT FK_Entry_Voucher FOREIGN KEY (VoucherID) REFERENCES T_Voucher(VoucherID), CONSTRAINT CK_Entry_Amount CHECK NOT (DebitAmt 0 AND CreditAmt 0) );逻辑说明三张表的关系是凭证头一分录。T_VoucherEntry 是核心每一行分录要么只记借方要么只记贷方不能同为零这是复式记账的基础约束。T_Voucher 里的 TotalDebit 和 TotalCredit 是冗余字段不存问题也不大但查询凭证列表时能少两次 SUM属于以空间换时间的常见做法。参数说明DECIMAL(18,2) 是财务系统金额字段的标配用 float 存的后果是月底对账差几分钱。AccountCode 用 VARCHAR 而非 INT因为科目编码允许前导零比如 1001 和 01001 是不同科目体系里的两个合法编码。IsLeaf 字段很关键——报表汇总时只汇总非叶子科目录凭证时只允许选叶子科目这套源码里很多校验逻辑都依赖这两个约定。3.2 凭证保存与借贷平衡校验业务层的核心逻辑财务系统最怕的就是保存一张借贷不平的凭证。源码里在 BLL 层做了两道校验第一道在前台截断输入后检查借方合计是否等于贷方合计第二道在后台事务里插入头和明细后再查一次合计。public bool SaveVoucher(VoucherDTO voucher, out string error) { error string.Empty; if (voucher.Entries.Count 2) { error 凭证至少需要两条分录; return false; } decimal debitTotal voucher.Entries.Sum(e e.DebitAmt); decimal creditTotal voucher.Entries.Sum(e e.CreditAmt); if (debitTotal ! creditTotal) { error $借贷不平衡借方 {debitTotal:N2}贷方 {creditTotal:N2}; return false; } using (SqlConnection conn new SqlConnection(_connStr)) { conn.Open(); SqlTransaction tran conn.BeginTransaction(); try { string insertVoucher INSERT INTO T_Voucher(VoucherDate, VoucherNo, AttachCount, TotalDebit, TotalCredit, Maker, AuditStatus, CreateTime) VALUES(date, no, attach, debit, credit, maker, 0, GETDATE()); SELECT CAST(SCOPE_IDENTITY() AS INT);; SqlCommand cmd new SqlCommand(insertVoucher, conn, tran); cmd.Parameters.AddWithValue(date, voucher.VoucherDate); cmd.Parameters.AddWithValue(no, voucher.VoucherNo); cmd.Parameters.AddWithValue(attach, voucher.AttachCount); cmd.Parameters.AddWithValue(debit, debitTotal); cmd.Parameters.AddWithValue(credit, creditTotal); cmd.Parameters.AddWithValue(maker, voucher.Maker); int voucherId (int)cmd.ExecuteScalar(); string insertEntry INSERT INTO T_VoucherEntry(VoucherID, LineNo, AccountCode, Summary, DebitAmt, CreditAmt) VALUES(vid, line, account, summary, debit, credit);; foreach (var entry in voucher.Entries) { cmd new SqlCommand(insertEntry, conn, tran); cmd.Parameters.AddWithValue(vid, voucherId); cmd.Parameters.AddWithValue(line, entry.LineNo); cmd.Parameters.AddWithValue(account, entry.AccountCode); cmd.Parameters.AddWithValue(summary, entry.Summary); cmd.Parameters.AddWithValue(debit, entry.DebitAmt); cmd.Parameters.AddWithValue(credit, entry.CreditAmt); cmd.ExecuteNonQuery(); } tran.Commit(); return true; } catch (Exception ex) { tran.Rollback(); error 保存失败 ex.Message; return false; } } }逻辑说明先算借方差额再决定是否走数据库目的是尽早拦截明显错误避免无谓的事务开销。真正进数据库后把头表和分录表放进同一个事务任一条插入失败就整体回滚。SCOPE_IDENTITY() 拿凭证 ID再把 ID 传给每条分录避免循环里再查一次。参数说明voucher.Entries.Sum 用的 LINQ 的 Sum 聚合对 decimal 类型是安全的。注意这里比较用的是 ! 而不是差值绝对值小于 0.01实际项目里如果前端允许输入三位小数这里的校验要改成 Math.Round(debitTotal, 2) 后再比。对这种边界情况我一般会在 DTO 里统一截断为两位小数而不是在 BLL 层各做各的。3.3 从源码学 C#委托、集合与 WPF 界面的落点这套财务系统源码对 C# 学习者来说价值不只是业务逻辑。它的界面层如果基于 WinForms 或 WPF你可以在里面找到事件委托的典型用法——比如凭证表格里修改金额单元格后自动刷新合计行这个动作就是标准的事件订阅。又比如科目树加载用的是递归遍历 DataTable 按 ParentID 分组这是集合操作最常见的实战场景。再往底层看数据库访问层如果使用了 SqlHelper 风格的封装Command 对象、DataAdapter、DataTable 之间的配合基本就是 C# 连接 SQL Server 的标准写法。很多 C# 教程把 SqlCommand 和 SqlDataReader 讲得很细但真实项目里大量用 DataAdapter DataTable因为后者能直接绑到 DataGridView界面代码少很多。这套源码正好能让你看到两种方式各自出现在哪一层。C# 上位机开发和 C# CAN 通讯里常见的“后台线程轮询设备状态、回调刷新 UI”的模式和这套系统里“后台定时刷新凭证列表”用的是同一套技术——BackgroundWorker 或 Task 加 Control.BeginInvoke。读源码时不需要只盯财务业务把线程切换、委托、事件这三条线抽出来后面再做 TCPListener 多客户端、做上位机都能复用。换句话说这套源码是一份披着财务外衣的 C# 基础实战手册。4. 避坑五个常见问题与排查思路4.1 SQL Server 登录失败认证模式与连接串的匹配现象工具启动后报“用户 trace_user 登录失败”或者连接到本地库成功、连服务器库失败。原因最常见的是两类。一是 SQL Server 实例是 Windows 认证模式没有启用混合认证模式SQL 账号根本不存在二是老版本源码里连接字符串走的是 SqlClient 旧协议默认加密行为在 SQL Server 2019 之后会握手失败。解决先在 SSMS 里确认实例认证模式。用 sa 或管理员账号执行下面这段把认证模式改成混合再给 trace_user 开通 ALTER TRACE 权限USE master; GO -- 开启混合认证模式1为Windows认证2为SQL Server和Windows认证 EXEC xp_instance_regwrite NHKEY_LOCAL_MACHINE, NSoftware\Microsoft\MSSQLServer\MSSQLServer, NLoginMode, REG_DWORD, 2; GO -- 创建专用跟踪账号避免直接使用sa CREATE LOGIN trace_user WITH PASSWORD 一个足够复杂的密码; -- 如果不想给sysadmin至少给ALTER TRACE GRANT ALTER TRACE TO trace_user;逻辑说明LoginMode 设为 2 代表 SQL Server 和 Windows 混合认证。ALTER TRACE 是创建跟踪的最小权限但 free-sql-server-profiler 源码里如果需要读系统视图和 fn_trace_gettable还要额外授权 VIEW SERVER STATE。给 sysadmin 是图省事的做法内网测试环境可以这么干生产环境别这么干。4.2 trc 文件体积爆炸过滤条件才是第一道防线现象监控跑了半天C 盘满SQL Server 报“跟踪文件写入失败”。原因没有设置过滤条件又把所有事件类型都勾上了。默认会抓 Lock:Deadlock、Lock:Timeout、SQL:BatchStarting 等高频事件Starting 事件的数量是 Completed 事件的几十倍。解决改配置只保留两类 Completed 事件其他全关再把 MinDurationMs 调到 1000 以上。给个判断参考真实业务每秒 50 个批处理的情况下只抓 SQL:BatchCompleted 加 1000ms 阈值过滤一天大概产生几百 MB如果连 Starting 一起抓同样时间可能几个 GB。4.3 默认密码不对哈希存储导致的版本错位现象编译完财务系统用文档里写的 admin/admin123 登录报密码错误。原因源码里初始化的管理员密码是用 MD5 加密后存进数据库的文档里写的是明文而初始化 SQL 脚本里插入的哈希对应的是另一个密码版本之间对不上。解决不要猜密码直接改数据库。财务系统登录校验常见做法是把用户输入的密码做 MD5 后再比对所以你可以自己算一个 MD5 更新进去-- 先在C#侧或任意工具里计算 Admin2025 的MD5值 -- string md5 MD5.HashData(Encoding.UTF8.GetBytes(Admin2025)); -- 把算出的哈希值更新到用户表 UPDATE T_User SET PasswordHash 这里填你算出的MD5值 WHERE UserName admin;逻辑说明MD5 本身不安全现代项目建议升级成 SHA256 加盐但老源码普遍是 MD5改登录逻辑比改数据库麻烦。注意编码UTF8 和 GB2312 对同一个字符串算出来的 MD5 不一样要和源码里 Encoding 默认编码保持一致。4.4 跟踪结果里看不到自己执行的 SQL现象工具显示了事件但只有登录登出没有自己的查询语句。原因跟踪创建在服务器 A 上但你的 SSMS 连的是服务器 B或者你连的是默认实例工具连的是命名实例还有一种常见情况是数据库过滤写错了库名大小写或下划线不一致。解决先在工具里打开事件过滤面板把 DatabaseFilter 清空再跑一条带明显标记的 SQL比如 SELECT hello_trace_2025。五秒后刷新跟踪列表如果能看到这条说明链路通看不到就从连接字符串入手把 Server 改成 IP,端口 形式绕开实例名解析。4.5 编译报错SqlClient 新旧两套库别混用现象源码里有部分文件用的是 SqlConnection编译时提示找不到类型或版本冲突。原因SqlClient 在 .NET Framework 和 .NET Core 里属于不同包。老项目在 .NET Framework 下用 System.Data.SqlClient 没问题升级到 .NET 6 之后官方把 SqlClient 迁移到 Microsoft.Data.SqlClient命名空间和程序集都变了。解决两种择一。要么项目保持 .NET Framework 4.x直接用 System.Data.SqlClient什么都不用改要么升级到 .NET 6全项目替换 using并安装 Microsoft.Data.SqlClient 的 NuGet 包。不建议混用混用会导致同一个连接串在两套客户端下行为不一致连 TLS 版本都各自为政。5. 组合实战用免费 Profiler 给财务系统做一次慢 SQL 体检5.1 体检流程启动跟踪、跑业务、汇总结果前面两章把两个资源拆开讲了这一章把它们串起来——用 free-sql-server-profiler 盯住财务系统的数据库找出凭证保存慢、报表查询卡的原因。体检分三步启动跟踪、按业务场景操作、收集结果。第一步启动跟踪。把 MinDurationMs 调成 300先用低阈值把数据量抓足。第二步在财务系统里跑典型业务新建凭证、保存凭证、打开科目余额表、跑月末结转。每步操作前后记住时间。第三步停止跟踪用 2.3 节的解析代码把 trc 文件读成 DataTable再按 Duration 排序取 Top 50 语句。第三步的汇总查询长这样SELECT TOP 50 TextData, CAST(Duration / 1000.0 AS DECIMAL(12,2)) AS DurationMs, CAST(CPU / 1000.0 AS DECIMAL(12,2)) AS CpuMs, Reads, Writes, StartTime, SPID FROM #ParsedTrace WHERE EventClass IN (10, 12) ORDER BY Duration DESC;逻辑说明EventClass 10 和 12 分别是 RPC:Completed 和 SQL:BatchCompleted只统计 Completed 才拿得到真实执行时长。Reads 是逻辑读次数这个值高往往比 Duration 更能说明索引问题。把结果按 Reads 排序对比 Duration 排序的结果能看出哪些语句是“跑得慢但读得少”的 CPU 密集型哪些是“读得多”的 IO 密集型两类问题的优化方向完全不同。5.2 财务系统典型的慢 SQL 特征与修复用这套流程体检下来财务系统里最容易暴露的问题基本集中在三类。第一类是凭证分录批量插入时的逐条 INSERT。如果 T_VoucherEntry 有几千行在循环里逐条 ExecuteNonQuery每次都要走一次网络往返和数据解析。对比 3.2 节的代码单凭证场景下没问题但批量导入凭证场景下必须改 SqlBulkCopy。第二类是报表模块的三表 JOIN 没有索引。科目余额表要关联科目表、凭证表和分录表如果 T_VoucherEntry 上缺 (VoucherID, AccountCode) 的复合索引月末跑一次试算平衡表可能消耗几十秒。第三类是 LIKE 模糊查询比如按摘要搜“办公费”如果写成 WHERE Summary LIKE %办公费%等于放弃索引全表扫。遇到前三类问题在开发库上是看不出来的因为数据量小。把跟踪结果导出来按 Reads 排序你会看到同一张表在几千行和几十万行时的执行计划完全是两个世界。这就是为什么我坚持让跟踪阈值先设低、再慢慢收紧——第一轮抓全量快慢第二轮才抓慢 SQL。5.3 从 SPID 和时间戳回到 C# 代码定位拿到慢 SQL 之后下一步是回到源码里定位。这时 profiler 里这两个字段是关键SPID 和 StartTime。SPID 是数据库会话 ID对应 C# 程序里某条 SqlConnection 打开的会话。如果项目关闭了连接池每次操作都是独立 SPID定位精度有限如果开启连接池同一 SPID 可能承载多个请求。常见做法是结合 StartTime往前找几十毫秒和程序日志里的时间戳对齐基本能锁定是哪次点击、哪段代码发的这条 SQL。更直接的办法是直接改 C# 代码在 SqlCommand 执行前把 CommandText 打到日志文件再和 trc 的 StartTime 比对。要注意的是trc 里的 TextData 是数据库端收到的最终 SQL而程序日志里可能是带参数的原始 SQL如果批量执行时参数被拼接两者对不上是正常的对时间即可。数据层里如果用了存储过程trc 的 EventClass 会是 RPC:CompletedTextData 显示的是存储过程名参数在后面的列里。这种情况建议在工具界面过滤条件里增加 ObjectName 等于存储过程名只抓目标过程可以减少大量干扰行。6. 进阶技巧把跟踪结果自动汇总成每日 TOP SQL 报表这套工具的默认用法是“手动开、手动关、手动分析”但 trc 文件是会自动滚动的只要 SQL Server 服务在跟踪就在写。如果你把它当成常驻监控每天会落好几个 trc 文件手工打开一个个解析不现实。我一般会在工具外层挂一个 Windows 计划任务每天凌晨先把昨天的 trc 按日期重命名拷贝到归档目录再执行下面的汇总 SQL。-- 昨晚的trc文件归档到每日目录后执行 -- 先建一个历史统计表避免重复分析 CREATE TABLE dbo.TraceDailySummary ( StatDate DATE NOT NULL, SqlTextHash BINARY(20) NOT NULL, -- SQL文本的SHA1用于分组同一语句 ExecCount INT NOT NULL, AvgDurationMs DECIMAL(12,2) NOT NULL, MaxDurationMs INT NOT NULL, TotalReads BIGINT NOT NULL, SampleSql NVARCHAR(MAX), CONSTRAINT PK_TraceDaily PRIMARY KEY (StatDate, SqlTextHash) ); -- 聚合语句按平均时长排序取前20条 INSERT INTO dbo.TraceDailySummary SELECT CAST(StartTime AS DATE) AS StatDate, HASHBYTES(SHA1, TextData) AS SqlTextHash, COUNT(*) AS ExecCount, AVG(CAST(Duration AS BIGINT) / 1000.0) AS AvgDurationMs, MAX(CAST(Duration AS BIGINT) / 1000) AS MaxDurationMs, SUM(Reads) AS TotalReads, MAX(TextData) AS SampleSql FROM fn_trace_gettable(D:\TraceArchive\2025-01-15.trc, 20) WHERE EventClass IN (10, 12) AND TextData IS NOT NULL AND Duration 100000 -- 只统计超过100ms的 GROUP BY CAST(StartTime AS DATE), HASHBYTES(SHA1, TextData) HAVING COUNT(*) 3 -- 去掉偶发语句只看高频慢语句 ORDER BY AvgDurationMs DESC;逻辑说明HASHBYTES(SHA1, TextData) 把同一条 SQL 的不同执行聚到一组统计执行次数和平均时长。HAVING COUNT(*) 3 是为了把只跑一次的初始化查询过滤掉那种语句重放价值低。Duration 大于 100000 微秒即 100 毫秒一般业务系统低于这个值不构成瓶颈。参数说明fn_trace_gettable 的第二个参数是 20代表最多读取 20 个滚动文件如果前一天 trc 超过 20 个要先合并或调整归档策略。SampleSql 取 MAX(TextData) 是因为分组后同组 SQL 文本基本一致取一条做展示足够。生产环境建议把结果表按周清理只保留 30 天毕竟 TextData 字段可能很大长期堆积会拖慢后续插入。汇总表出来后我会写一个简单的 WinForms 小窗体启动时读当天汇总按 AvgDurationMs 降序显示前 20 行双击某行再展开 SampleSql 全文。这样一来每天早上到工位第一件事就是扫一眼这个列表看看有没有新出现的慢语句。曾经有一次月末结转特别慢我就是靠这个列表发现是 T_VoucherEntry 缺了 AccountCode 的单列索引而开发库数据量小一直没有暴露。从那以后凡是涉及财务系统的 SQL 变更我强制自己先用这套 trace 工具跑一遍前后对比看时长和 Reads 两个指标有没有回退再决定合不合并代码。希望这个组合用法也能帮到你。本文还有配套的精品资源点击获取
返回列表