祥积宫 无限进步

【DB2 数据库】03 模拟故障排查系列:表空间打满后的扩容与恢复

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

上一篇文章中,我已经通过一个很小的 DMS 表空间 TS2,成功复现了“表空间被打满”的故障现象。

这一篇文章继续沿着同一个实验环境往下做,重点解决下面几个问题:

  • 表空间打满之后,如何判断故障还在
  • 表空间打满之后,如何通过扩容恢复
  • 扩容前后应该重点观察哪些指标
  • 为什么扩容后 High water mark 不会自动变小
  • 为什么扩容后业务可以立刻恢复写入

如果说上一篇重点是“怎么构造故障”,那么这一篇重点就是:

故障发生后,如何通过扩容把库恢复回来。

二、实验环境

  • 操作系统: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
  • 扩容后新增容器:/DB2_DATA/TESTDB/ts2/ts2_02.dat

本次实验基于上一篇已经搭建好的环境继续进行:

  • 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                           = 160
Free pages                           = 1792
High water mark (pages)              = 160
Page size (bytes)                    = 4096
Extent size (pages)                  = 32
Prefetch size (pages)                = 32
Number of containers                 = 1

当时 T3 已经重新创建回来,并且库里已有一条测试数据。

也就是说,这次实验的起点是:

  • TS2 可写
  • TS2 只有一个容器
  • TS2 还没有再次被打满

五、先确认扩容前的容器状态

在真正开始恢复实验之前,我先再次确认 TS2 当前只有一个容器。

执行命令:

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

实验时真实输出如下:

Tablespace Containers for Tablespace 5

Container ID                         = 0
Name                                 = /DB2_DATA/TESTDB/ts2/ts2_01.dat
Type                                 = File
Total pages                          = 2000
Useable pages                        = 1952
Accessible                           = Yes

这一步很关键,因为后面扩容是否成功,最直观的判断依据之一就是:

  • 容器数有没有从 1 变成 2

六、再次把 TS2 打满

为了验证扩容动作是否真的能恢复业务,我没有直接扩容,而是先让 TS2 再次进入“打满”状态。

持续插入数据:

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

这段脚本的目的很直接:

  • 不断向 TS2 中的 T3 插入大字段数据
  • 直到表空间再次耗尽可分配页

七、打满之后该看什么

这类故障发生后,我最关注的不是先去猜 SQL 报错,而是立刻看表空间状态。

核心命令:

db2 "select count(*) from T3"
# 查看当前已经成功插入了多少条数据

db2 list tablespaces show detail
# 查看 TS2 的详细状态,重点观察 Free pages

db2 list tablespace containers for 5 show detail
# 再确认故障发生时 TS2 仍然只有一个容器

在这一步里,最重要的判断点是:

  • Free pages 是否已经变成 0

因为只要 Free pages = 0,就说明当前表空间已经没有新的可分配页。

八、恢复前的核心判断

表空间打满后的恢复动作,不应该一上来就盲目执行。

应该先回答两个问题:

1. 是不是表空间真的满了

看:

  • Used pages
  • Free pages

2. 现在有没有多余容器

看:

  • Number of containers
  • list tablespace containers

这一步的意义在于:

如果当前只有一个容器,而且 Free pages = 0,那么最直接的恢复方式就是:

增加新的容器

九、准备扩容用的新容器路径

这次实验里,我选择的扩容方式不是修改现有容器大小,而是:

ADD 一个新的容器

先确保路径存在,并且实例用户有权限:

mkdir -p /DB2_DATA/TESTDB/ts2
# 确保 TS2 容器目录存在

chown -R db2inst1:db2iadm1 /DB2_DATA/TESTDB/ts2
# 把目录属主设为实例用户

chmod 755 /DB2_DATA/TESTDB/ts2
# 确保目录权限正常

虽然这次的新容器仍然放在同一路径下,但它已经是一个新的容器文件:

  • ts2_02.dat

十、通过 ADD 新容器扩容 TS2

执行扩容命令:

db2 "alter tablespace TS2 add (file '/DB2_DATA/TESTDB/ts2/ts2_02.dat' 2000)"
# 给 TS2 增加第二个容器文件
# 容器大小同样是 2000 页

这一步属于:

手工扩容

不是自动扩容,也不是调整自动扩容策略,而是立刻给表空间增加一个新的物理容器。

十一、扩容后立刻验证

扩容完成后,我没有马上插数据,而是先看表空间状态是否真的变化了。

1. 先看容器数

db2 list tablespace containers for 5 show detail
# 再次查看 TS2 的容器详情

2. 再看表空间整体信息

db2 list tablespaces show detail
# 查看 TS2 的 Total pages / Free pages / Number of containers

这次实验里,扩容后的真实状态是:

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

这里最关键的变化是:

  • Total pages:从 2000 变成了 4000
  • Free pages:从 0 恢复成了 1952
  • Number of containers:从 1 变成了 2

这已经足以说明扩容是成功的。

十二、为什么 High water mark 没有变小

这一步很容易让人困惑。

很多人看到扩容后会下意识以为:

  • 总空间变大了
  • 空闲页也回来了
  • 那么 High water mark 也应该变小

其实不是。

High water mark 表示的是:

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

所以它记录的是“历史峰值”,不是“当前是否已经恢复”。

这次实验中,扩容后它仍然是:

  • High water mark = 1952

这是完全正常的。

所以要注意:

  • 看当前还能不能继续分配页,重点看 Free pages
  • 看历史上曾经用到多满,重点看 High water mark

十三、扩容后为什么业务可以立刻恢复

扩容成功后,我继续执行了一条插入:

db2 "insert into T3 values (999999, repeat('R',3000))"
# 扩容后再次插入一条大记录,验证能否恢复写入

实验结果是:

DB20000I  The SQL command completed successfully.

这说明:

扩容后的 TS2 已经重新具备可写能力,业务恢复成功。

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

通过这次实验,已经可以清楚地得出下面这些结论:

  1. 当表空间打满时,只要 Free pages = 0,表空间就已经没有新的可分配页
  2. 如果当前表空间是手工容器,并且只剩一个容器,那么最直接的恢复方式之一就是 ADD 新容器
  3. 扩容成功后,Total pagesFree pagesNumber of containers 都会发生变化
  4. High water mark 不会因为扩容而自动下降,因为它表示历史峰值
  5. 只要新的空闲页已经出现,业务就可以继续写入

十五、这篇实验和上一篇的衔接关系

上一篇讲的是:

  • 表空间是怎么被打满的
  • 删除表后为什么页可以复用
  • 为什么空间不会自动返还给操作系统

这一篇讲的是:

  • 表空间再次打满后
  • 如何通过增加新容器完成恢复
  • 恢复后哪些字段会变,哪些字段不会变

所以这两篇连起来,刚好构成一个完整闭环:

  1. 先制造表空间打满
  2. 再通过扩容恢复业务

十六、为什么这次选择 ADD,而不是直接讲 EXTEND

理论上,表空间扩容可以有多种方式:

  • ADD:新增容器
  • EXTEND:扩大已有容器
  • AUTORESIZE:让系统按策略自动扩

但这次实验我优先选择 ADD,原因很简单:

  • 容器数变化最直观
  • 扩容前后差异最好观察
  • 后续写“容器路径异常”“不同盘扩容”等主题时也更好衔接

对入门故障模拟来说,ADD 是最适合先讲清楚的一种方式。

十七、这篇文章最值得记住的三句话

把整篇实验压缩成三句话,就是:

  1. 表空间打满后,先看 Free pages 是否为 0。
  2. 通过 ADD 新容器扩容后,Free pages 会恢复,业务可以继续写入。
  3. High water mark 记录的是历史峰值,不会因为扩容而自动下降。

十八、下一篇最自然写什么

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

  • 继续沿表空间路线,讲 EXTENDAUTORESIZE
  • 切换到另一类故障,例如日志空间不足或容器权限异常

如果继续顺着当前脉络往下走,那么最适合的下一篇是:

【DB2 数据库】04 模拟故障排查系列:表空间扩容方式对比(ADD、EXTEND、AUTORESIZE)

这样整个系列会非常成体系。

Linux