祥积宫 无限进步

【DB2 数据库】02 模拟故障排查系列:构造TS2表空间打满并恢复

一、这篇文章要解决什么问题

在 DB2 日常运维里,“表空间打满”是非常常见、也非常适合拿来做入门演练的一类故障。

这一篇文章基于真实实验,完整复现了下面这个过程:

  • 手工创建一个很小的表空间 TS2
  • TS2 中创建测试表 T3
  • 持续插入大字段数据,直到表空间被打满
  • 观察表空间打满时的状态
  • 删除测试表后,验证空间是否恢复可用
  • 进一步解释“为什么表删了,但底层文件系统空间没有自动变小”

这不是一篇只给命令的操作稿,而是一篇把实验现象、排查思路和概念理解放在一起的故障模拟文章。

二、实验环境

  • 操作系统:SUSE Linux Enterprise Server 12 SP5
  • DB2 版本:DB2 v9.7.0.6 Fix Pack 6
  • 实例:db2inst1
  • 数据库:TESTDB
  • 用于故障演练的目标表空间:TS2
  • 容器路径:/DB2_DATA/TESTDB/ts2/ts2_01.dat

在上一篇文章中,我们已经完成了:

  • TESTDB 的创建
  • 默认表空间 USERSPACE1 的验证
  • 独立表空间 TS1 的创建和扩容

这一次为了更稳定地复现“表空间打满”,我没有直接拿 TS1 开刀,而是额外新建了一个更小的表空间 TS2

三、为什么要单独创建一个小表空间 TS2

如果直接拿现有较大的表空间去做“打满”实验:

  • 需要插入的数据量会更多
  • 现象可能不够稳定
  • 对已有实验对象影响更大

所以这次我专门创建了一个容量很小的独立表空间 TS2,这样做有几个好处:

  • 更容易快速打满
  • 故障现象更集中
  • 回滚和恢复更简单
  • 更适合写成博客给读者复现

四、准备 TS2 的容器目录

先用 root 准备容器目录:

mkdir -p /DB2_DATA/TESTDB/ts2
# 创建 TS2 的容器目录

chown -R db2inst1:db2iadm1 /DB2_DATA/TESTDB/ts2
# 把目录属主改成实例用户,确保 DB2 有权限创建容器文件

chmod 755 /DB2_DATA/TESTDB/ts2
# 赋予正常目录权限

ls -ld /DB2_DATA/TESTDB/ts2
# 确认目录状态

五、创建一个很小的表空间 TS2

切到实例用户后,连接数据库:

su - db2inst1
# 切换到 DB2 实例用户

db2 connect to TESTDB
# 连接到 TESTDB

创建表空间:

db2 "create regular tablespace TS2 pagesize 4 K managed by database using (file '/DB2_DATA/TESTDB/ts2/ts2_01.dat' 2000) bufferpool IBMDEFAULTBP"
# 创建一个很小的普通表空间 TS2
# 容器只有 2000 页,页大小是 4KB

验证表空间是否创建成功:

db2 "select tbspaceid, tbspace from syscat.tablespaces where tbspace = 'TS2'"
# 查询 TS2 的编号和名称

这次实验的真实结果是:

  • TS2TBSPACEID = 5

所以后面查看容器时,我直接使用:

db2 list tablespace containers for 5 show detail

六、关闭 TS2 的自动扩容

这次实验的目标是稳定制造“表空间打满”,所以不希望表空间自己偷偷长大。

执行:

db2 "alter tablespace TS2 autoresize no"
# 明确关闭 TS2 的自动扩容

然后查看容器:

db2 list tablespace containers for 5 show detail
# 查看 TS2 当前的容器

此时 TS2 只有一个容器:

  • /DB2_DATA/TESTDB/ts2/ts2_01.dat

七、创建测试表 T3,并明确放到 TS2

db2 "create table T3(id int not null, c1 varchar(3000)) in TS2"
# 创建测试表 T3,并明确指定落在 TS2

db2 "select tabschema, tabname, tbspace from syscat.tables where tabname = 'T3'"
# 验证 T3 的所属表空间

实验时验证结果为:

  • T3 落在 TS2

为了先确认表本身没有问题,可以先少量插入:

db2 "insert into T3 values (1, repeat('A',3000))"
# 插入第一条测试数据

db2 "insert into T3 values (2, repeat('B',3000))"
# 插入第二条测试数据

db2 "select count(*) from T3"
# 确认当前写入正常

八、持续插入数据,构造表空间打满

接下来通过循环持续插入大字段数据:

i=3
while [ $i -le 50000 ]
do
  db2 "insert into T3 values ($i, repeat('X',3000))" || break
  i=$((i+1))
done

这段脚本的作用很简单:

  • 不断向 TS2 里的 T3 插入较大的记录
  • 一旦插入失败就立刻停止

由于 TS2 容量非常小,所以很快就能把它打满。

九、表空间打满后的真实现象

这次实验里,表空间详细信息中 TS2 的状态如下:

Tablespace ID                        = 5
Name                                 = TS2
Type                                 = Database managed space
Contents                             = All permanent data. Regular table space.
State                                = 0x0000
  Detailed explanation:
    Normal
Total pages                          = 2000
Useable pages                        = 1952
Used pages                           = 1952
Free pages                           = 0
High water mark (pages)              = 1952
Page size (bytes)                    = 4096
Extent size (pages)                  = 32
Prefetch size (pages)                = 32
Number of containers                 = 1

这里最关键的两个字段是:

  • Used pages = 1952
  • Free pages = 0

这已经足以说明:

TS2 在实验当时已经被打满。

也就是说,表空间已经没有多余的可用页再继续分配。

十、判断表空间打满时,重点看哪些字段

很多人第一次看 list tablespaces show detail 时会被很多字段绕晕,其实这类实验先重点看 4 个就够了:

1. Total pages

表空间总页数。

2. Used pages

当前已经使用掉的页数。

3. Free pages

当前还能继续分配的空闲页数。

4. High water mark

表空间历史上曾经用到的最高水位。

在这次实验里:

  • Free pages = 0

这就是最直观的故障信号。

十一、恢复动作:删除测试表 T3

为了恢复实验环境,我执行了:

db2 "drop table T3"
# 删除测试表 T3,释放其占用的页

这一步是本次实验最值得继续深入理解的地方。

很多人第一反应会以为:

drop table 之后,底层文件系统空间应该立刻回来。

但真实情况并不是这样。

十二、为什么删表后,底层文件系统空间没有自动变小

这是本次实验里最有价值的结论之一。

更准确地说:

删除表之后,空间先回到了 DB2 表空间内部,可被数据库重新分配使用;但不会自动返还给操作系统文件系统。

也就是说,这里发生的是两层不同的“释放”:

第一层:DB2 内部释放

  • T3 不再占用这些页
  • 这些页重新回到 TS2 内部
  • 后续其他对象还可以继续使用这些页

第二层:OS 文件系统释放

  • 容器文件 ts2_01.dat 真的缩小
  • 磁盘空间回到操作系统可用空间

这次实验中,发生的是第一层,没有发生第二层。

也正因为如此,哪怕删了表,通常也不会看到容器文件在操作系统层面自动缩回去。

一句话总结:

数据删掉后,空间先回 DB2,不会自动回 OS。

十三、如何验证空间确实已经回到表空间内部

为了验证“空间是可复用的”,我没有停留在理论上,而是继续做了一个非常关键的实验:

重新创建 T3,并再次插入数据。

db2 "create table T3(id int not null, c1 varchar(3000)) in TS2"
# 重新创建 T3

db2 "insert into T3 values (1, repeat('A',3000))"
# 再插入一条数据

实验结果是:

DB20000I  The SQL command completed successfully.
DB20000I  The SQL command completed successfully.

这就证明了一件事:

虽然操作系统文件空间没有自动缩小,但 TS2 内部的页已经重新变成可用状态,可以再次写入。

这个验证比单纯看概念更有说服力。

十四、这次实验到底说明了什么

通过这次实验,我们实际验证了下面这些结论:

  1. 可以通过“很小的 DMS 表空间 + 大字段循环插入”稳定复现表空间打满
  2. 判断是否打满,重点看 Free pages
  3. Free pages = 0 时,表空间已经没有可继续分配的页
  4. 删除表后,空间优先回到 DB2 表空间内部,而不是立即返还给底层文件系统
  5. 表空间容器文件不会因为删表而自动缩小
  6. 只要页已经被 DB2 重新纳入可分配范围,就可以继续建表、继续写入

十五、这篇实验和上一篇有什么衔接关系

如果说上一篇文章重点解决的是:

  • 默认表空间和独立表空间的区别
  • 表、Schema、表空间、容器到底是什么关系

那么这一篇文章重点解决的是:

  • 表空间真的满了会是什么样
  • 该怎么看 list tablespaces show detail
  • 为什么“删表了”不等于“OS 空间马上回来”

也就是说,这一篇已经从“环境搭建”开始进入真正的“故障模拟与排查”阶段了。

十六、这次实验特别适合写给谁

这篇文章特别适合下面几类读者:

  • 刚接触 DB2 运维的人
  • 知道表空间概念,但对 DMS 行为不够直观的人
  • 以为删表后文件系统空间一定会立刻回来的读者
  • 想搭一个可重复、可回滚、可写博客的 DB2 故障实验环境的人

十七、下一篇可以继续做什么

基于目前环境,下一篇最自然的方向有几个:

  • TS2 扩容并恢复业务
  • 模拟容器权限异常
  • 模拟日志空间不足
  • 模拟实例未启动或异常终止

如果继续沿着表空间这条线往下走,那么最适合的下一篇就是:

表空间打满之后,如何扩容并恢复

这样整套系列会非常顺。

Linux