SQL Server 2008的数据库在运行一段时间后,日志文件突然膨胀到几十个GB,或者事务日志备份一直无法截断,这类问题十有八九和某个迟迟不提交的事务有关。事务一旦开启,即使里面只执行了一条SELECT语句,日志截断点也会被它拖住,后续所有日志都无法清理。定位这类问题的第一步,就是找出数据库里存活时间最长的事务。

用DBCC OPENTRAN快速定位最旧的活动事务
DBCC OPENTRAN是SQL Server提供的一个经典命令,从很早的版本就开始存在。它的作用是显示指定数据库中最旧的活动事务以及最旧的分布式事务信息,包括服务器进程ID、登录用户名、事务开始时间等关键信息。对于排查日志无法截断的问题,这个命令往往是最快的切入点,因为它直接回答了两个问题:谁在占用,占了多久。
使用时需要先切换到目标数据库的上下文,然后执行命令。默认情况下它返回最旧的事务,如果想看指定数量的事务,可以带上WITH TABLERESULTS参数让结果以表格形式输出,便于程序化处理。
USE MyDatabase; GO -- 查看当前数据库中最旧的活动事务 DBCC OPENTRAN; GO -- 以表格形式输出,方便程序处理 DBCC OPENTRAN WITH TABLERESULTS; GO
执行结果通常包含TranDID、SessionID、LoginName、Transaction Begin Time等字段。其中Transaction Begin Time尤其重要,用当前时间减去它,就是这个事务已经存活的时长。如果发现一个事务已经挂了几个小时甚至几天,那基本可以确定它就是日志暴涨的元凶。需要注意的是,DBCC OPENTRAN只能显示最旧的那一个事务,如果数据库里同时存在多个未提交事务,还需要借助动态管理视图进一步排查。
结合动态管理视图查询会话详细信息
SQL Server 2008引入了一整套基于DMV的监控体系,比旧版的sysprocesses要清晰得多。要全面了解当前所有活动事务,可以从sys.dm_tran_active_transactions、sys.dm_tran_session_transactions和sys.dm_exec_sessions这几个视图入手。sys.dm_tran_session_transactions把会话和事务关联起来,是中间的桥梁。
下面这个查询把所有有未完成事务的会话全部列出来,附带会话开始时间、主机名、登录名以及事务开始的准确时间。通过host_name字段还能知道是哪台客户端机器发出的请求,这对定位是哪个业务系统、哪台应用服务器造成的问题非常有帮助。
SELECT s.session_id,
s.host_name,
s.login_name,
s.program_name,
s.status,
t.transaction_begin_time,
DATEDIFF(minute, t.transaction_begin_time, GETDATE()) AS open_minutes
FROM sys.dm_tran_session_transactions st
JOIN sys.dm_exec_sessions s
ON s.session_id = st.session_id
JOIN sys.dm_tran_active_transactions t
ON t.transaction_id = st.transaction_id
ORDER BY t.transaction_begin_time;知道会话ID之后,下一步是看这个会话到底在执行什么SQL。把结果和sys.dm_exec_requests以及sys.dm_exec_sql_text配合起来,可以直接拿到正在执行的语句文本。这一点很关键,因为只有看到SQL内容,才能判断这个事务是业务代码漏写了提交,还是有人开了事务之后去吃午饭忘了关闭。
-- 查看某个会话当前正在执行的语句
SELECT r.session_id,
r.blocking_session_id,
r.wait_type,
r.wait_time,
st.text AS running_sql
FROM sys.dm_exec_requests r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.session_id = 55; -- 替换为实际查到的会话ID此外,sys.dm_exec_sessions里的last_request_start_time和last_request_end_time也能反映会话的活跃程度。如果last_request_end_time是NULL且status为running,说明该会话正在执行请求。把这些信息组合起来,就能拼出完整的事故现场。
处理未提交事务的正确姿势与预防措施
找到问题会话之后,处理方式需要谨慎选择。最直接的办法是用KILL命令终止会话,SQL Server会自动回滚该会话的事务。但要特别注意,回滚一个长时间运行的事务本身可能耗时很久,因为回滚进度和事务大小成正比。在终止之前最好先通知相关开发人员确认业务状态,避免误杀正在执行重要批量操作的会话。
-- 终止指定会话,事务会被自动回滚 KILL 55; -- 对于SQL2008,可以查看回滚进度 SELECT session_id, percent_complete, command FROM sys.dm_exec_requests WHERE session_id = 55;
如果KILL的对象是应用程序连接池里的会话,杀掉之后连接池会重建连接,一般不会造成太大影响。但如果杀的是维护作业或者批量导入任务,可能需要重新执行整个作业。因此在生产环境操作前,建议先把会话的程序名program_name记录下来,判断它属于哪个应用。
预防永远比补救重要。开发层面要养成良好习惯:显式事务必须配对COMMIT和ROLLBACK,用TRY CATCH包裹事务逻辑,在异常分支中回滚;避免在事务内部执行耗时的外部调用,比如调用Web服务或者等待人工输入。可以参考下面的规范写法,确保任何路径下事务都能正确关闭。
BEGIN TRY
BEGIN TRANSACTION;
UPDATE Accounts SET balance = balance - 100 WHERE id = 1;
UPDATE Accounts SET balance = balance + 100 WHERE id = 2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
THROW; -- 注意:SQL2008不支持THROW,此处用RAISERROR替代
END CATCH需要说明的是,SQL Server 2008并不支持THROW语句,实际使用RAISERROR来抛出错误。运维层面则可以部署定期巡检脚本,每隔几分钟执行一次上面的DMV查询,发现有事务存活超过设定阈值就发邮件告警。同时给tempdb和事务日志设置合理的告警阈值,这样即使问题再次出现,也能在日志空间耗尽之前介入处理,把影响控制在最小范围。
SQL2008DBCC OPENTRAN事务查询修改时间:2026-09-13 16:07:28