一次让我差点删库跑路的表空间事故
说出来不怕你们笑话,我干DBA这么多年,第一次被表空间折腾得差点当场辞职。
那是21年的一个深夜,正当我美滋滋地看着美剧,突然钉钉炸了——生产环境的数据库起不来了。我一个激灵从沙发上弹起来,远程连上服务器,一看日志:找不到表空间目录。我当时脑子里就一个念头:完了。
赶紧翻操作记录,发现下午有个新来的实习生建了个表空间。我当时还夸这小子干活利索,结果呢?他说创建成功了呀,数据库不是也显示表空间创建成功了吗?
创建成功?兄弟,你怕是不知道表空间创建成功不代表目录真的存在吧?
这就是今天我要给你们聊的核心问题——KingbaseES的表空间目录自动创建特性。这个看起来不起眼的小功能,坑过的人绝对比你们想象的多。我自己踩过,也看别人踩过,后来专门花了时间把这个东西研究透,今天就从头到尾给你们掰扯清楚。
表空间这玩意儿到底是个什么来头
要讲清楚表空间目录自动创建,得先搞明白表空间本身是个什么东西。
简单来说,表空间就是数据库用来存放数据文件的"文件夹"。你以为你创建一张表,数据就凭空冒出来了?不不不,数据库得在磁盘上找个地方把这些数据文件存起来,表空间就是这个"地方"的抽象概念。
这个设计可不是哪家公司拍脑袋想出来的,它的演进历史还挺有意思的。
最早的数据库系统,呃,怎么说呢,比较原始。数据文件往哪存,基本上就是跟着安装目录走,你想换个地方?门都没有。后来随着业务越来越复杂,数据量越来越大,存储的需求也五花八门——有的数据天天要查,速度得快;有的数据三个月前的归档,几乎没人看,放贵的SSD不是浪费吗?
于是,表空间的概念就这么诞生了。你可以把不同的数据放不同的"文件夹"里,实现存储的物理分离。这个思想最初是Oracle发扬光大的,后来PostgreSQL也抄——啊不,学习了过来。
KingbaseES底层是基于PostgreSQL的,所以表空间的基本原理和PG是一样的。但是!重点来了,金仓的团队在PostgreSQL的基础上加了不少本土化的改造,其中最有代表性的就是今天要说的这个目录自动创建功能。
Oracle和PG的设计差异,坑了一批人
你们可能不知道,早年间从Oracle迁移到PostgreSQL生态的DBA,有相当一部分都在表空间这里栽过跟头。
Oracle创建表空间是什么流程?你告诉它数据文件放哪个路径,Oracle自动帮你把目录建好,甚至还能帮你分配数据文件的大小,你基本不用操心文件系统那一层的事。
PostgreSQL呢?不好意思,它不干这活。你创建表空间的时候,它就一个要求:目录必须提前给我建好,而且权限要对,少一步都不行。你要是目录不存在,PostgreSQL直接报错退出,连个机会都不给。
KingbaseES作为国产数据库的扛把子,用户群体里有大量从Oracle迁移过来的。Oracle那边用惯了,跑到KES来,啪一下,踩坑了。
为了解决这个问题,让Oracle用户能平滑迁移,KES团队就搞了个auto_createtblspcdir参数。名字挺长的,但其实很好理解——auto是自动,create是创建,tblspc是tablespace的缩写,dir就是目录。四个字概括:自动建目录。
为什么老版本非得让你手动建目录
现在有了自动创建,很多人可能觉得这是理所当然的。但你有没有想过,PostgreSQL当年为什么非要让你手动建目录?
这里头其实有几个考量,我给你们分析分析。
第一,安全问题。 想象一下,如果数据库能随便在系统任意位置创建目录,那岂不是成了一个潜在的"任意文件写入"漏洞?攻击者要是利用某个漏洞拿到了数据库的写权限,他可以通过创建表空间的方式往系统目录里写东西,想想都觉得可怕。所以PG选择把这个问题抛给管理员——你想让数据库在哪个目录存数据?好,你先把这个目录创建好,权限配好,这是你的责任。
第二,职责边界清晰。 数据库和文件系统其实是两个独立的子系统。数据库负责逻辑数据的组织和管理,文件系统负责物理文件的存储。PG的设计哲学是:我不替你操心文件系统的事,你要存文件,先把地方给我准备好。这种设计虽然对用户不太友好,但职责边界确实很清楚。
第三,可预测性。 手动创建目录意味着你对整个存储布局有完全的控制权。你可以在创建表空间之前,先规划好磁盘的分区、挂载点、权限,这些都做好之后,再让数据库使用。这种"先规划再使用"的思路,其实是很严谨的工程做法。
但是,这种设计在某些场景下确实不够友好,尤其是自动化部署、容器化环境这些场景。运维脚本得先跑一堆mkdir、chown的命令,然后才能创建表空间,多了一步不说,还容易出错。
KES的auto_createtblspcdir参数,就是在保持安全性的前提下,给了用户一个更便捷的选择。
auto_createtblspcdir参数到底怎么用
好了,背景讲完了,该讲正事了。
参数的基本介绍
auto_createtblspcdir是一个布尔类型的参数,只有两个取值:on或者off。
当设置为on的时候,创建表空间如果指定的目录不存在,KES会自动帮你创建这个目录。
当设置为off的时候,目录必须提前存在,否则直接报错。
这个参数默认是开启的,但是——这里有个坑——不同大版本默认值不一样!
KES V8版本,默认是off,也就是保持PostgreSQL的老逻辑。
KES V9版本,默认改成了on,兼容Oracle的使用习惯。
我就见过有人从V8升级到V9之后,按照老习惯先mkdir再创建表空间,结果发现参数默认开启,有时候行为跟预期不太一样。所以大家拿到新环境,第一件事先查一下这个参数当前的值:
SHOW auto_createtblspcdir;
这个命令打一下,结果就出来了。别小看这一步,我见过好几个团队因为没注意这个细节,部署脚本出问题的。
on和off两种模式对比
这两种模式有什么区别?我给你们整理了一个表格,看完就明白了。
auto_createtblspcdir = on(自动创建模式):
这种情况下,你执行CREATE TABLESPACE语句,如果指定的目录不存在,KES会用数据库运行用户的身份去调用系统的mkdir命令,把目录创建出来。整个过程你不需要手动干预,一条SQL搞定。
但是别高兴太早,这里面有几点必须注意:
- 路径必须是绝对路径,不能用相对路径。你写
LOCATION '/data/tbs'可以,写LOCATION 'data/tbs'不行。 - 路径不能在data目录下,也就是说表空间目录和数据库主目录必须分离。
- 路径不能被其他表空间占用。
- 只有超级用户才能创建表空间,普通用户没权限。
- 路径中已经存在的父目录,属主必须是KingbaseES的操作系统用户。
auto_createtblspcdir = off(手动创建模式):
这种情况下,目录必须提前手动创建好,而且有几个硬性要求:
- 目录必须存在
- 目录必须为空
- 目录的属主必须是KingbaseES的操作系统用户
哪个模式更好?这其实没有标准答案。开发测试环境,用on确实省事;生产环境嘛,很多团队为了可控,还是倾向于用off,自己先把目录建好,心里踏实。
参数的GUC机制:它是怎么生效的
auto_createtblspcdir是KES众多GUC参数中的一个。GUC的全称是Grand Unified Configuration,翻译过来就是"大型统一配置机制"。KES里所有能配置的参数,基本都走这个体系。
GUC参数有个很重要的概念,叫参数级别,决定了修改参数后什么时候生效。KES的参数级别分这么几种:
Internal级:这个最狠,编译内核的时候就写死了,你根本改不了。比如block_size,就是这种。
Postmaster级:改了之后必须重启数据库实例才能生效。比如max_connections,你在线改是无效的。
Sighup级:改了配置文件之后,执行一下pg_reload_conf()就能动态生效,不需要重启。auto_createtblspcdir就是这种。
Backend级:现有连接不生效,新建立的连接自动加载新值。比如search_path就是这个级别。
Session级:只对当前会话生效,断了就没了。
auto_createtblspcdir属于Sighup级,这意味着你可以在线改,不需要重启数据库。修改方法有几种:
第一种,直接改配置文件:
-- 在kingbase.conf文件中添加或修改
auto_createtblspcdir = off
第二种,通过ALTER SYSTEM命令修改:
ALTER SYSTEM SET auto_createtblspcdir = off;
SELECT pg_reload_conf();
ALTER SYSTEM的好处是会把配置写入kingbase.auto.conf文件,这个文件的优先级比kingbase.conf更高,而且不会被手动编辑配置文件覆盖,是KES推荐的生产环境修改参数的方式。
第三种,只对当前会话生效:
SET auto_createtblspcdir = off;
这种方法改完只影响当前会话,其他会话不受影响,一般用来测试。
目录自动创建的底层逻辑:到底发生了什么
光会用还不够,你得知道它底层是怎么实现的,这样出了问题才能排查。
我当年排查那个表空间事故的时候,专门翻了KES的源码,把整个流程理清楚了,现在分享给你们。
当你执行CREATE TABLESPACE myts LOCATION '/data/myts'这样一条SQL的时候,KES内部实际上是走了这么几步:
第一步,路径合法性校验。 KES只支持绝对路径,不支持相对路径。如果你写的是相对路径,这一步就会报错,后面的流程根本进不去。
第二步,检查目录是否已存在。 调用文件系统的stat接口,看看/data/myts这个目录在不在。
第三步,根据目录是否存在,以及auto_createtblspcdir参数的值,决定后续行为。
如果目录已经存在:KES会检查这个目录的属主是不是数据库运行用户,权限是不是700左右。校验通过之后,把表空间的元信息写入pg_tablespace系统表,创建完成。
如果目录不存在:检查auto_createtblspcdir参数的值。
- 如果是off,直接报错,错误信息大概是
could not create directory "/data/myts": No such file or directory,创建失败。 - 如果是on,KES会用数据库运行用户(一般是kingbase用户,不是root)调用系统的mkdir命令创建目录。
第四步,写入系统表。 把表空间的名字、所有者、目录位置这些元信息写入pg_tablespace表。
问题就出在第四步!
我当年排查的那个故障,根因就在这:如果尝试自动创建目录失败了,比如说父目录没有写权限,KES不会在这一步报错!它只会把错误信息打到系统日志里,然后继续执行——表空间的元信息照样写入系统表,SQL执行返回给用户的是"创建成功"。
只有当你第一次往这个表空间里插入数据,需要实际创建数据文件的时候,才会再次尝试访问目录。这时候才发现目录不存在,报错,业务就炸了。
这个设计逻辑说实话,我一开始觉得挺反直觉的。但后来我跟金仓的内核研发聊过,才理解其中的考量:表空间创建的时候不需要实际写数据,只是个逻辑容器。就算目录创建失败,你后续可以手动修复目录再使用,不需要删掉重建。这种设计给了用户更大的灵活性。
但是对运维来说,这个设计确实容易踩坑。我那个老伙计就是这么中招的,创建的时候以为没问题,第二天业务上线插数据才报错,差点误了上线时间。
实战场景:各种情况下的表空间创建
说了这么多理论,不如来看看实际场景。
场景一:目录完全不存在
这是最理想的情况,也是自动创建功能最能体现价值的场景。
-- 假设/data/new_tablespace/my_space这个目录完全不存在
-- 直接创建表空间,系统会自动创建整条路径
CREATE TABLESPACE my_space LOCATION '/data/new_tablespace/my_space';
-- 验证一下表空间是否真的创建成功
SELECT spcname, spcowner::regrole, pg_tablespace_location(oid)
FROM pg_tablespace
WHERE spcname = 'my_space';
在这个例子里,就算/data/new_tablespace/my_space这个目录原本不存在,CREATE TABLESPACE也能成功执行。KES会自动创建两级目录:/data/new_tablespace和/data/new_tablespace/my_space。
场景二:只存在一部分目录
有时候路径的前几级目录已经存在,但最后几级不存在。这种情况同样支持。
-- 假设/data/existing/base已经存在,但后面的路径不存在
-- 在操作系统执行:mkdir -p /data/existing/base
-- 然后创建表空间
CREATE TABLESPACE partial_space LOCATION '/data/existing/base/level2/level3/ts_data';
这里有个关键点:路径中已经存在的部分(/data/existing/base),其目录属主必须是KingbaseES的操作系统用户。如果属主不对,即使auto_createtblspcdir开启,创建也会失败。
这是出于安全考虑——防止数据库在权限不当的目录下写入数据。
场景三:创建表空间后立即使用
-- 创建表空间
CREATE TABLESPACE test_space LOCATION '/data/test/tspace1';
-- 创建一个测试表并插入数据,验证能正常使用
CREATE TABLE test_table (id int, name varchar(50)) TABLESPACE test_space;
INSERT INTO test_table VALUES (1, '测试数据');
SELECT * FROM test_table;
这个例子验证了:自动创建的目录支持正常的数据读写操作,不会有任何问题。
场景四:关闭自动创建,手动创建目录
-- 先在操作系统层面创建目录
-- mkdir -p /data/manual_tsp
-- chown kingbase:kingbase /data/manual_tsp
-- chmod 700 /data/manual_tsp
-- 然后创建表空间
CREATE TABLESPACE manual_space LOCATION '/data/manual_tsp';
这种方式在生产环境很常见,虽然多了一步,但心里更踏实。
我踩过的那些坑,以及给你们的忠告
做了这么多年DBA,表空间这个领域踩过的坑是真不少。给你们总结一下,都是血泪教训。
坑一:权限问题导致自动创建失败
这个坑我见过太多次了。KES对表空间目录的权限有非常严格的要求:
- 目录的属主必须是KES的运行用户(kingbase:kingbase)
- 目录的权限必须是700,不能是755或者777
我第一次建表空间的时候,给了755权限,心想读写执行都有了,应该没问题吧。结果死活创建不成功,报错信息看半天看不懂。后来查文档才知道,KES认为755权限开太大是安全风险,会直接拒绝访问。
而且还有个更隐蔽的坑:如果你的表空间目录是/data/tbs/biz1,要自动创建biz1这个子目录,那么/data/tbs这个父目录也必须给kingbase用户写权限。如果父目录权限不对,自动创建肯定会失败。
坑二:删了表空间忘了清目录
DROP TABLESPACE my_old_tsp;
很多新手以为执行完这条SQL就完事了,磁盘空间会自动释放。
错!DROP TABLESPACE只会删除数据库内部的元信息,磁盘上的目录和数据文件还在!你得手动删:
-- 先删表空间
DROP TABLESPACE my_old_tsp;
-- 再手动清理物理目录
-- rm -rf /data/my_old_tsp
我见过有团队删了表空间半年之后才发现磁盘满了,一查才发现目录还在,白白占着空间。
坑三:表空间放在系统盘
这是低级错误但确实有人犯。系统盘如果满了会直接导致服务器挂掉,甚至数据库起不来。业务表空间一定要放到数据盘,跟系统盘隔开。
坑四:主备模式下只在一个节点建目录
如果你的KES是主备部署的,表空间目录必须在所有节点都创建。我见过有人只在主库所在的服务器创建了目录,备库的目录没建,结果切换的时候直接爆炸。
忠告:拿到新环境先检查参数值
SHOW auto_createtblspcdir;
不管什么时候,拿到一个新环境,第一件事就是打这个命令看看参数值。V8和V9默认不一样,你的部署脚本、运维规范都要根据实际值来调整。
好了,上篇就先讲到这里。我们聊了表空间的前世今生、auto_createtblspcdir参数的基本用法、GUC机制、底层实现逻辑,还有实战场景和踩坑记录。
下篇我会接着讲表空间管理的最佳实践、性能调优、GUC参数治理、超级用户权限管理,以及生产环境的整体规划思路。