【DB2 数据库】10 模拟故障排查系列:长事务占用日志后的强制回滚与恢复验证实验
一、这篇文章要解决什么问题
前面第 08 篇我已经验证过 LOG FULL。
这一次,我想继续往下看一层。
如果日志不是单纯“打满”,而是被某个长事务一直占着,我应该怎么把它找出来,再把它强制结束掉。
这篇实验我重点确认 4 件事:
- 哪个连接才是真正占住日志的会话
db2 list applications show detail应该怎么看force application之后会发生什么- 数据和日志最后是不是都能恢复
二、实验环境
- 操作系统:
SUSE Linux Enterprise Server 12 SP5 - DB2 版本:
DB2 v9.7.0.6 Fix Pack 6 - 实例:
db2inst1 - 数据库:
TESTDB - 实验表:
TLOG3 - 实验表空间:
USERSPACE1
这次实验里,我把 TLOG3 放在 USERSPACE1。
这样做的目的很简单。
我不想让一个太小的表空间先把实验带偏。
三、先把环境确认一遍
在正式开始前,我先确认实例是活的,数据库也能连。
# 先确认实例管理器已经可用
db2start返回结果是:
SQL1026N The database manager is already active.这说明实例已经起来了。
# 看 TESTDB 是否已经编目
db2 list db directory结果里能看到:
Database alias = TESTDB
Database name = TESTDB
Local database directory = /DB2_DATA
Directory entry type = Indirect再连一次库确认一下:
# 连接数据库,确认 TESTDB 正常
db2 connect to TESTDB
db2 terminate返回也正常:
Database Connection Information
Database server = DB2/LINUXX8664 9.7.6
SQL authorization ID = DB2INST1
Local database alias = TESTDB
DB20000I The TERMINATE command completed successfully.到这里,我先确认了一件事:
当前环境是可以继续做实验的。
四、先把实验表建好
我先确认 TLOG3 在不在。
# 先尝试删掉旧表,避免历史数据干扰
db2 drop table TLOG3这一步提示表不存在。
所以我直接重新建表:
# 把实验表建到 USERSPACE1,避免小表空间先满
db2 "create table TLOG3(id int not null, c1 varchar(3000)) in USERSPACE1"建完以后,我再查一次表空间归属。
# 确认 TLOG3 的表空间位置
db2 "select tabschema, tabname, tbspace from syscat.tables where tabname='TLOG3'"结果能看到:
TABSCHEMA TABNAME TBSPACE
DB2INST1 TLOG3 USERSPACE1我这里一开始还顺手把 TBSPACE 写错成了 TABSPACE,先报了一个 SQL0206N。
这个坑很基础,但很值得记一下。
syscat.tables 里正确的列名是 TBSPACE。
五、先把长事务做出来
接下来,我进入 db2 => 提示符,开始制造一个长事务。
这里我踩过一个小坑。
在 db2 => 里,SQL 最好直接一整句输入,不要把 ; 带上。
我第一次带了分号,先报了 SQL0104N。
后来我把语句写成单行,不带分号,就正常了。
第一批数据我这样插:
insert into TLOG3 select 100000 + row_number() over(order by c1.colname, c2.colname), repeat('A',3000) from syscat.columns c1, syscat.columns c2 fetch first 5000 rows only第二批我换一个 ID 区间:
insert into TLOG3 select 200000 + row_number() over(order by c1.colname, c2.colname), repeat('B',3000) from syscat.columns c1, syscat.columns c2 fetch first 5000 rows only两批都成功了。
这一步的意义是:
我已经把一笔还没提交的大事务放进去了。
六、我怎么找到真正要处理的会话
接下来,我要找出到底是哪一个连接占着这笔事务。
我用的是:
# 看当前连接的详细信息,重点找 db2bp
db2 list applications show detail输出里有一行最关键:
DB2INST1 db2bp 64 *LOCAL.db2inst1.260508030320 00001 1 0 16 UOW Waiting ... TESTDB我当时看的重点不是一堆 db2fw*。
我只盯两个字段:
Application Name = db2bpStatus = UOW Waiting
这就说明:
真正占着未提交事务的,就是这个交互式会话。
对应的句柄就是 64。
七、我把这笔长事务强制结束掉
找到句柄以后,我就直接处理它。
# 句子里带括号,所以外层要加双引号
db2 "force application (64)"这个命令成功后,我马上查表确认结果。
# 查 TLOG3 的行数,确认 force 之后是否回滚
db2 "select count(*) from TLOG3"结果变成了:
1
-----------
0
1 record(s) selected.这一步很关键。
它说明前面那笔长事务确实被回滚了。
也就是说,日志占用的根因已经被我处理掉了。
八、为什么 force 之后,db2pd -logs 反而读不到了
我 force 完以后,再去看日志,发现数据库已经不再是 active 状态了。
这时候执行:
# 看日志状态
db2pd -db TESTDB -logs会提示:
Database TESTDB not activated on database partition 0.我把这个现象记下来,是因为它很容易让初学者误判。
这不是故障。
这是因为我刚刚把那笔长事务强制结束后,数据库已经退回到非激活状态了。
我再查了一次 db2 list applications,旧会话也已经没有了。
这时返回的是:
SQL1611W No data was returned by Database System Monitor.要重新读日志,先把数据库重新连起来。
九、重新激活 TESTDB,再看日志状态
我重新连库以后,再看日志就正常了。
# 重新连接数据库,TESTDB 会再次激活
db2 connect to TESTDB
# 这次再看日志,数据库就能被读取了
db2pd -db TESTDB -logs这次 db2pd 的状态已经变成:
Database Partition 0 -- Database TESTDB -- Active -- Up 0 days 00:00:02 -- Date 2026-05-08-11.38.08.552109日志也已经恢复成正常状态。
我重点看了这几个字段:
Current Log Number 0
Pages Written 0
Log Chain ID 0
Current LSN 0x000000000CF28010下面的日志文件也回到了正常列表。
从 S0000000.LOG 到 S0000012.LOG 都还在。
这说明日志链路没有被破坏,数据库只是回到了正常可用状态。
十、再确认一次活动连接和表数据
重新激活以后,我再看一次活动连接。
# 看当前活跃连接
db2 list applications这时看到的是我后来重新连库产生的新会话:
Auth Id Application Name Appl. Handle Application Id DB Name # of Agents
DB2INST1 db2bp 113 *LOCAL.db2inst1.260508033806 TESTDB 1这个 113 不是前面那个被 force 的 64。
它只是我重新连接数据库后新开的验证会话。
然后我再查一次 TLOG3:
# 确认刚才那批未提交数据已经被回滚
db2 "select count(*) from TLOG3"结果还是:
1
-----------
0
1 record(s) selected.这说明我前面制造的那批数据确实没有留下来。
十一、这次实验说明了什么
这次实验跑完,我把几个结论记得很清楚。
第一,真正占日志的,往往不是背景进程,而是那个还没提交的 db2bp 会话。
第二,db2 list applications show detail 的重点不是把表看全,而是先找 UOW Waiting。
第三,force application (64) 之后,未提交事务会回滚,TLOG3 的行数会回到 0。
第四,db2pd -db TESTDB -logs 如果提示数据库没激活,先重新 connect,不要先怀疑日志坏了。
第五,在 db2 => 里写 SQL,一定要一整句输入,别把分号带进去。
十二、实验结束后的收尾
最后我用下面两步结束会话:
# 结束当前会话
db2 terminate如果后面还要继续做日志实验,我会保留 TLOG3。
这次的重点已经不是建表,而是:
我已经知道怎么定位长事务、怎么强制结束它、以及怎么验证日志已经恢复。