祥积宫 无限进步

【DB2 数据库】01 模拟故障排查系列:从零搭建TESTDB并理解表空间与Schema

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

这篇文章不是单纯记录“怎么创建一个数据库”,而是基于一次真实实验,把下面几个初学时最容易混淆的问题一次理顺:

  • DB2 实例、数据库、Schema、表空间,到底是什么层级关系
  • 新建数据库后,表默认落在哪个表空间
  • 为什么生产环境通常不会长期直接用默认 USERSPACE1
  • 如何手工创建独立表空间,并指定容器路径
  • 表空间扩容有哪些方式
  • ADDEXTENDAUTORESIZEINCREASESIZE 到底有什么区别

这篇文章里的结论,全部来自我在实验环境中的实际操作和即时验证,不是脱离环境的概念堆砌。

二、实验环境

  • 操作系统:SUSE Linux Enterprise Server 12 SP5
  • DB2 版本:DB2 v9.7.0.6 Fix Pack 6
  • 实例:db2inst1
  • 新建实验库:TESTDB
  • 数据目录:/DB2_DATA
  • 备份目录:/DB2_BACKUP

这次实验不是从“创建实例”开始,而是复用现有实例 db2inst1,在空实例环境上重新创建一个简单名字的数据库 TESTDB

三、先厘清层级关系

这一步很重要,因为如果一开始把层级搞反,后面看表空间、Schema 和容器都会越看越乱。

正确关系是:

  • instance 管数据库运行
  • database 管对象集合
  • schema 管对象命名归属
  • tablespace 管对象物理存放

可以先记成:

实例 > 数据库 > Schema > 表

tablespace 不在这条命名层级链上,它更像是“对象实际住在哪里”。

一个更准确的类比

  • db2inst1 是实例
  • TESTDB 是数据库
  • DB2INST1APPHR 这种是数据库里的 Schema
  • T1T2 是具体表
  • USERSPACE1TS1 是表空间

也就是说:

  • DB2INST1.T1 表示逻辑名字
  • USERSPACE1TS1 表示物理存放位置

一句话记忆:

Schema 决定表叫什么全名,tablespace 决定表住在哪里。

四、从空环境创建 TESTDB

1. 确认实例存在,数据库目录为空

先切到实例用户:

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

确认实例和数据库目录状态:

db2 get instance
# 查看当前实例名,确认是 db2inst1

db2 list db directory
# 查看当前实例下已经编目的数据库

实验时这里返回的是空目录,说明可以从头开始建新库。

2. 创建新数据库

db2 "create db TESTDB on /DB2_DATA"
# 在 /DB2_DATA 上创建一个新数据库 TESTDB

3. 验证数据库能正常连接

db2 list db directory
# 再次查看数据库目录,确认 TESTDB 已经存在

db2 connect to TESTDB
# 连接到 TESTDB

db2 "select current server from sysibm.sysdummy1"
# 验证当前连接到的数据库名

db2 terminate
# 结束当前 DB2 会话

到这里为止,实验库 TESTDB 就创建完成了。

五、默认表会落到哪里

1. 创建一个最简单的测试表 T1

db2 connect to TESTDB
# 连接 TESTDB

db2 "create table T1(id int not null, name varchar(50))"
# 创建测试表 T1,这里没有显式指定表空间

db2 "insert into T1 values (1, 'A')"
# 插入第一条数据

db2 "insert into T1 values (2, 'B')"
# 插入第二条数据

db2 "select * from T1"
# 查询测试数据

2. 查看 T1 实际属于哪个表空间

db2 "select tabschema, tabname, tbspace from syscat.tables where tabname = 'T1'"
# 查看 T1 的 schema 和 tablespace

这次实验的真实结果是:

  • T1tabschemaDB2INST1
  • T1tbspaceUSERSPACE1

也就是说,如果建表时没有写 in 某个表空间,普通业务表会默认落在 USERSPACE1

3. 查看 USERSPACE1 的容器路径

db2 list tablespace containers for 2 show detail
# 查看表空间 ID=2,也就是 USERSPACE1 的底层容器路径

实验时查到的真实路径是:

/DB2_DATA/db2inst1/NODE0000/TESTDB/T0000002/C0000000.LRG

所以此时 T1 的完整归属可以写成:

  • 逻辑名字:DB2INST1.T1
  • 所在表空间:USERSPACE1
  • 底层容器:/DB2_DATA/db2inst1/NODE0000/TESTDB/T0000002/C0000000.LRG

六、为什么生产里通常要自己建独立表空间

只用默认 USERSPACE1 当然能跑,但不适合长期生产运维。

常见原因有:

  • 所有业务对象混在一起,不利于后续扩容
  • 做容量规划时边界不清楚
  • 出现表空间满、容器异常时,不容易快速定位
  • 不方便把不同业务、不同对象类型分层管理

所以生产里通常会自己规划:

  • 数据表空间
  • 索引表空间
  • LOB 表空间
  • 临时表空间

而不是把所有业务表长期都扔到默认表空间里。

七、手工创建独立表空间 TS1

这一步是整篇实验最关键的部分之一。

1. 先准备容器目录

root 创建目录:

mkdir -p /DB2_DATA/TESTDB/ts1
# 创建 TS1 的容器目录

chown -R db2inst1:db2iadm1 /DB2_DATA/TESTDB/ts1
# 把目录属主改成实例用户

chmod 755 /DB2_DATA/TESTDB/ts1
# 赋予正常访问权限

2. 创建 TS1

su - db2inst1
# 切回实例用户

db2 connect to TESTDB
# 连接数据库

db2 "create regular tablespace TS1 pagesize 4 K managed by database using (file '/DB2_DATA/TESTDB/ts1/ts1_01.dat' 20000) bufferpool IBMDEFAULTBP"
# 创建普通表空间 TS1
# pagesize 4K:页大小 4KB
# managed by database:由 DB2 管理表空间
# using file ... 20000:使用一个文件容器,大小 20000 页

3. 验证 TS1 是否创建成功

这次实验里,我一开始写错了查询字段名。

错误写法:

db2 "select tbsp_id, tbsp_name from syscat.tablespaces where tbsp_name = 'TS1'"

在这台 DB2 9.7 上,这条会报错,因为正确字段不是 tbsp_id/tbsp_name,而是:

  • TBSPACEID
  • TBSPACE

正确写法:

db2 "select tbspaceid, tbspace from syscat.tablespaces where tbspace = 'TS1'"
# 查询表空间编号和名字

本次实验查到:

  • TS1TBSPACEID = 4

4. 查看 TS1 的容器路径

db2 list tablespace containers for 4 show detail
# 查看表空间 ID=4 的容器详情

实验时容器路径为:

/DB2_DATA/TESTDB/ts1/ts1_01.dat

八、创建一张明确落在 TS1 的表 T2

1. 创建表并指定表空间

db2 "create table T2(id int not null, name varchar(50)) in TS1"
# 创建测试表 T2,并明确指定放到 TS1

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

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

2. 验证 T2 确实落在 TS1

db2 "select tabschema, tabname, tbspace from syscat.tables where tabname = 'T2'"
# 查看 T2 所在表空间

db2 "select * from T2"
# 查询 T2 数据

实验时真实结果是:

  • T2tabschema = DB2INST1
  • T2tbspace = TS1

于是这两张表的区别就很清楚了:

  • T1 -> 默认 USERSPACE1
  • T2 -> 手工指定 TS1

九、一个常见疑问:查询表时为什么不用指定表空间

这是实验过程中最有价值的一组问答之一。

很多人第一次接触表空间时都会疑惑:

我明明只是执行了:

db2 connect to TESTDB
db2 "select * from T1"

又没有说“去哪个表空间里查”,DB2 怎么知道该去哪?

答案是:

你连的是数据库,不是表空间。

DB2 查询表时,先根据系统目录判断:

  • 这张表叫什么
  • 属于哪个 Schema
  • 物理上放在哪个表空间

也就是说,表空间对普通 SQL 查询是透明的

查询关注的是:

  • 表名是谁
  • Schema 是谁

而不是:

  • 这个表放在哪个表空间

表空间更多是运维、存储规划和故障处理时才需要关注的维度。

十、再解释一次 Schema

Schema 是这次实验里另一个最容易和表空间混淆的概念。

可以先把它理解成:

Schema 是数据库里的逻辑命名空间。

它的作用不是决定表放哪,而是决定表叫什么全名。

例如:

  • DB2INST1.T1
  • APP.T1

这两个表名虽然都叫 T1,但因为 Schema 不同,所以可以共存。

所以更准确地说:

  • 同一个 Schema 里,不能有两个同名表
  • 不同 Schema 里,可以有同名表

这也是为什么 select * from T1 很多时候能成功,因为当前用户的默认 Schema 恰好就是 DB2INST1

十一、给 TS1 扩容:先做手工扩容

接下来开始进入运维动作。

1. 查看 TS1 当前容器数量

db2 list tablespace containers for 4 show detail
# 查看 TS1 当前的容器

最开始只有一个容器:

  • ts1_01.dat

2. 新增第二个容器

db2 "alter tablespace TS1 add (file '/DB2_DATA/TESTDB/ts1/ts1_02.dat' 20000)"
# 给 TS1 增加第二个容器文件

3. 验证扩容结果

db2 list tablespace containers for 4 show detail
# 再次查看 TS1 的容器

db2 list tablespaces show detail
# 查看 TS1 的总页数变化

实验结果符合预期:

  • TS1 从 1 个容器变成了 2 个容器
  • 总页数从 20000 增加到 40000

这一步属于:

手工扩容

十二、扩容时,容器能不能放在别的路径

可以。

对于这种手工定义容器路径的表空间来说:

  • 新容器完全可以放到另一条目录
  • 也可以放到另一块新盘

只要满足:

  • 路径存在
  • 实例用户有权限
  • 底层挂载正常

就可以加入表空间。

这也是生产环境里很常见的扩容方式。

十三、容器放在不同介质上会不会有问题

从“能不能工作”的角度看:

  • 可以工作
  • 不会因为一个在 SSD、一个在 HDD 就自动把数据库搞坏

但从运维和性能角度看,要谨慎:

  • 同一表空间里混用快慢差异很大的介质,性能往往会被慢盘拖住
  • 某块慢盘或不稳定盘出现空间不足、挂载丢失、权限异常时,整个表空间仍会受到影响

所以生产里更稳妥的做法通常是:

  • 同类介质尽量放在同一个表空间内
  • 热点对象单独规划更快的表空间

十四、ADDEXTENDINCREASESIZE 到底有什么区别

这是本次实验里最值得总结的一组概念。

1. ADD

db2 "alter tablespace TS1 add (file '/DB2_DATA/TESTDB/ts1/ts1_02.dat' 20000)"

含义:

  • 现在立刻新增一个容器
  • 表空间马上变大

2. EXTEND

含义:

  • 不是加新文件
  • 而是把已有容器变大

本质上也是:

手工立即扩容

3. INCREASESIZE

db2 "alter tablespace TS1 increasesize 10 percent"

它和 ADD/EXTEND 不一样。

它不是“现在立刻增加 10% 空间”,而是:

如果以后发生自动扩容,那么每次自动扩容按 10% 增长。

所以最准确的记法是:

  • ADD/EXTEND 是动作
  • INCREASESIZE 是规则

一句话总结:

ADD/EXTEND 是手工立即扩容,INCREASESIZE 是自动扩容时的增长策略。

十五、TS1 现在到底是手工管理,还是自动扩容

这个问题实验里也做了验证。

1. 创建 TS1 时的状态

最初创建 TS1 的命令是:

db2 "create regular tablespace TS1 pagesize 4 K managed by database using (file '/DB2_DATA/TESTDB/ts1/ts1_01.dat' 20000) bufferpool IBMDEFAULTBP"

这里的:

managed by database

很容易让人误以为“数据库会自动帮我扩容”。

实际上不是。

它更准确的含义是:

  • DB2 管理这个表空间内部页和 extent 的使用
  • 但不等于默认就自动扩容整个表空间

所以刚创建完成时,TS1 仍然属于:

  • 手工定义容器
  • 容量需要人为控制

2. 后续我们又开启了自动扩容策略

实验中执行并成功的命令有:

db2 "alter tablespace TS1 autoresize yes"
# 开启自动扩容

db2 "alter tablespace TS1 increasesize 10 percent"
# 每次自动扩容时按 10% 增长

所以到实验后半段,TS1 变成了:

  • 先手工扩过一次
  • 再开启了自动扩容策略

十六、一个很有价值的踩坑:DB2 9.7 的语法差异

这次实验还踩到了一个很重要的版本差异点。

我一开始尝试:

db2 "alter tablespace TS1 increasesize 5000 pages"

结果报错:

Expected tokens may include: "PERCENT"

说明在这台 DB2 9.7 上,INCREASESIZE 这里不接受 PAGES,而接受:

  • PERCENT

所以这条改成下面写法后成功:

db2 "alter tablespace TS1 increasesize 10 percent"

然后我又尝试:

db2 "alter tablespace TS1 maxsize 80000 pages"

依然报错,提示期望的是:

  • K

这说明在这台 DB2 9.7 上,MAXSIZE 更适合写容量单位:

  • K
  • M
  • G

这一点非常值得记下来,因为它不是纸面知识,而是实验现场真实踩出来的结论。

十七、这篇实验的最终结论

到这里,我们已经实际完成并验证了下面这些事情:

  1. 在空实例环境里创建了 TESTDB
  2. 建立了默认测试表 T1
  3. 验证了 T1 默认落在 USERSPACE1
  4. 手工创建了独立表空间 TS1
  5. 建立了明确落在 TS1 的测试表 T2
  6. 验证了表、表空间、容器三层对应关系
  7. TS1 做了一次手工扩容 ADD
  8. 验证了 AUTORESIZE + INCREASESIZE 的设置方式
  9. 实测发现了 DB2 9.7 的语法差异

十八、对初学者最重要的三句话

最后把整篇文章压缩成三句话:

  1. Schema 决定表叫什么全名,tablespace 决定表住在哪里。
  2. 不指定表空间时,普通业务表会默认落到 USERSPACE1。
  3. ADD/EXTEND 是手工扩容,INCREASESIZE 是自动扩容规则。

十九、下一篇可以接着写什么

基于这篇实验,下一篇最自然的方向有两个:

  • 继续做“表空间打满”故障演练
  • 继续做“容器路径异常/权限异常”故障演练

因为现在我们已经有了:

  • 一个默认表空间对象 T1
  • 一个独立表空间对象 T2
  • 一个可以继续操作的手工表空间 TS1

后续不管做容量故障还是容器故障,都已经有了非常合适的实验载体。

Linux 存储