Home

【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 = db2bp
  • Status = 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.LOGS0000012.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

这次的重点已经不是建表,而是:

我已经知道怎么定位长事务、怎么强制结束它、以及怎么验证日志已经恢复。

Linux DB2 数据库