表空间目录自动创建:从一个小开关聊到云原生存储的那些事
文章目录有个事儿得从刚入行不到两年说起这个坑我记得特别清楚auto_createtblspcdir 到底是个啥自动不等于随意——五条铁律来点实际的——各种场景跑一遍话说回来——底层到底怎么执行的GUC参数那些事儿——不只是改个值这个才是重点——容器化环境里咋整K8s里的PV/PVC和表空间S3兼容存储想都别想...呃也不是完全不行存储分层——这才是云原生的正确打开方式热数据上SSD冷数据上HDD临时表空间也别忘了动态扩容怎么办版本差异这事儿真的坑爹超级用户权限管理——别给多了踩坑实录——属主问题是最阴的一条SQL完成的快感最后啰嗦两句兼容是对前人努力的尊重是确保业务平稳过渡的基石然而这仅仅是故事的起点有个事儿得从刚入行不到两年说起我记得特别清楚那会儿刚入行不到两年有天晚上接到一个紧急需求要在生产环境加一个表空间。我当时心想这还不简单一条SQL的事嘛。结果啪啪打脸——CREATE TABLESPACE刚敲下去就报错目录不存在。大半夜的我又没有服务器的root权限只能给运维打电话让他帮忙建目录。运维老哥接了电话迷迷糊糊的建了目录还忘了chown属主给成了root。我又执行一次还是报错权限不对。来来回回折腾了四十多分钟最后运维老哥都烦了说你到底行不行。说实话那会儿我整个人都是懵的。一个表空间而已为啥搞得这么复杂后来才明白这就是数据库和操作系统之间那个谁都管不了谁的老问题——数据库想存东西但没有权限在文件系统里建目录操作系统有权限但它不关心你要建什么表空间。所以后来KES出了这个auto_createtblspcdir参数的时候我第一反应就是这玩意儿早该有了。这个坑我记得特别清楚早期的KES版本里创建自定义表空间是个两头跑的活。你先得登录服务器一层层把目录建好还得确保权限和属主都对然后再回到数据库里执行CREATE TABLESPACE。听着不复杂对吧但实际操作中只要中间有一步出错——目录少建了一层、属主给了root、权限设成755——语句直接报错前面的功夫全白费。而且这个流程在几个场景下特别让人崩溃批量初始化环境的时候脚本里要不断判断目录到底存不存在不存在就mkdir -p然后执行SQL代码越写越臃肿。自动化部署更别提了DevOps流水线要在操作系统层面和数据库层面反复切换编排复杂度直线上升。测试环境快速搭建临时建一个表空间光准备目录就得花好几分钟。多层嵌套路径手敲/data/ts/business/module1/submodule/sp1这种东西拼写错误几乎是必然的。KES的解决思路很直接——既然目录不存在是报错的根源那就让数据库自己把它建出来。auto_createtblspcdir 到底是个啥这个特性由一个GUC参数auto_createtblspcdir控制。先说个很多人不知道的点不同KES大版本的默认值不一样。V8默认是offV9默认改成了on。我接触过好几个新手就是因为没注意这个版本差异照着旧文档操作结果踩了坑。所以拿到新环境第一件事-- 先看看当前到底是啥值别想当然SHOWauto_createtblspcdir;这个参数是bool类型就两个值on和off。它属于Sighup级别的参数改完执行pg_reload_conf()就能生效不用重启数据库生产环境改起来很方便。开和关的行为差异其实挺大的on的时候V9默认目录不存在数据库就自动帮你递归创建整条路径路径中已经存在的父目录属主必须是数据库操作系统用户新建目录的属主自动设为数据库OS用户。off的时候呢目录不存在直接报错目录必须已经存在而且必须为空属主也必须对。相当于回退到传统模式所有准备工作都得你自己做。-- 修改参数推荐用ALTER SYSTEM变更会写入kingbase.auto.conf方便追踪ALTERSYSTEMSETauto_createtblspcdiroff;-- 不用重启reload就行SELECTpg_reload_conf();-- 确认一下SHOWauto_createtblspcdir;-- 想看参数的完整元信息包括当前值从哪来的SELECTname,setting,source,context,short_descFROMpg_settingsWHEREnameauto_createtblspcdir;话说回来大多数场景下建议保持默认的on。除非你有非常严格的目录管理规范——比如所有存储路径必须由专门的运维流程创建——才考虑关掉它。自动不等于随意——五条铁律自动创建虽然方便但KES对表空间设置了一整套约束无论参数开关怎么设这些规则都不能破。绝对路径是硬要求。数据库不接受相对路径因为相对于谁这个问题它没法回答-- 这才对CREATETABLESPACEmysp1 LOCATION/data/tablespaces/mysp1;-- 这要报错的CREATETABLESPACEmysp1 LOCATION./mysp1;-- ERROR: tablespace location must be an absolute path路径不能在data目录里面。这条限制是为了避免表空间文件和系统数据文件混在一起不然备份、迁移、权限管理都会乱成一锅粥-- 假设data目录是 /opt/Kingbase/ES/V8/dataCREATETABLESPACEmysp1 LOCATION/opt/Kingbase/ES/V8/data/mysp1;-- ERROR: tablespace location must not be inside the data directory一个路径只能绑一个表空间。防止命名冲突和资源争用CREATETABLESPACEsp1 LOCATION/data/ts/sp1;CREATETABLESPACEsp2 LOCATION/data/ts/sp1;-- ERROR: directory is already in use as a tablespace只有超级用户能创建表空间。普通用户想用表空间怎么办可以授权CREATE权限但不代表他能管理表空间本身-- 普通用户直接建表空间没门-- ERROR: must be superuser to create a tablespace-- 但可以这样授权让他能在表空间里建表CREATEROLE ops_admin LOGIN;GRANTCREATEONTABLESPACEts_dataTOops_admin;属主必须一致——这个坑最深。不管自动创建还是手动创建路径中已经存在的那些父目录属主必须是启动数据库的操作系统用户。最典型的翻车现场运维用sudo mkdir建了目录忘了chown然后创建表空间死活失败。# 检查属主ls-la/data/tablespaces/# drwx------ 2 kingbase kingbase 4096 ...# 如果属主不对赶紧修正sudochown-Rkingbase:kingbase /data/tablespaces/sudochmod700/data/tablespaces/来点实际的——各种场景跑一遍我有个哥们儿之前也是这问题各种目录情况都踩了一遍我直接把他的测试场景搬过来加上我自己的理解整理一下。场景一目录已经完整存在最简单的情况目录提前建好了直接建表空间就行-- 前提是目录已经存在属主也对了CREATETABLESPACEmysp1 LOCATION/data/tbs/mysp1;场景二只存在一部分目录这种情况挺常见的比如/data/tbs已经有了但/data/tbs/app/logs不存在。当auto_createtblspcdir开启的时候KES会自动把不存在的部分补全-- /data/tbs 存在/data/tbs/app/logs 不存在自动创建CREATETABLESPACEmysp_log LOCATION/data/tbs/app/logs;场景三完全不存在全路径自动创建这个是最方便的场景。路径完全不存在KES递归创建所有层级你啥都不用管-- 深层嵌套路径无所谓自动全建出来CREATETABLESPACEmysp_new LOCATION/data/tbs/app/year/month/day/mysp_new;场景四大小写混合建表写数据验证光建表空间不算完得验证数据能不能真正写进去才行-- 建表空间注意大小写混合的路径CREATETABLESPACEmysp_biz LOCATION/data/tbs/BizData;-- 在这个表空间上建表CREATETABLEorders(idINT,amountNUMERIC(12,2),created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP)TABLESPACEmysp_biz;-- 插数据试试INSERTINTOordersVALUES(1,9999.00,2026-01-15 10:30:00);INSERTINTOordersVALUES(2,1234.56,2026-01-16 14:22:00);INSERTINTOordersVALUES(3,55555.00,2026-02-01 09:00:00);-- 查一下确认没问题SELECT*FROMorders;-- 看看表空间的使用情况SELECTspcname,sys_size_pretty(sys_tablespace_size(oid))assizeFROMsys_tablespace;话说回来——底层到底怎么执行的这个应该…嗯…我记得是这么回事。当你执行CREATE TABLESPACE ... LOCATION xxx的时候内核里的流程大致是这样的首先校验LOCATION路径的合法性只支持绝对路径写相对路径直接报错。然后检查路径对应的目录是否已经存在。如果存在了校验目录权限通过就把表空间元信息写入系统表完事。如果不存在看auto_createtblspcdir参数——off就直接报错on的话用KES运行用户一般是kingbase用户调用系统mkdir创建目录。这里有个细节值得注意如果自动创建目录失败比如权限不够KES在某些情况下不会在CREATE TABLESPACE阶段报错而是把错误打在日志里仍然把表空间的元信息写入系统表返回创建成功。只有当你第一次往这个表空间的表插入数据、需要创建数据文件的时候才会再次访问目录找不到目录才会报错。这个设计挺…嗯…怎么说呢我一开始觉得挺反直觉的创建失败为什么不直接报错后来想想也能理解——表空间本质上是个逻辑容器创建的时候并不一定马上就要写文件。所以如果你建完表空间之后不太确定最好顺手验证一下-- 建完表空间后创建个测试表确认目录是真的可用的CREATETABLEtblspc_test(idint)TABLESPACEmysp_new;INSERTINTOtblspc_testVALUES(1);DROPTABLEtblspc_test;GUC参数那些事儿——不只是改个值既然说到参数我就多唠叨几句。auto_createtblspcdir是GUC体系下的一个参数而GUC是KES所有参数配置的核心框架。很多人对GUC一知半解改参数改错了还不知道为什么。KES的GUC参数大概有几百个按生效方式分成五个级别Internal级是内核编译时写死的改不了。Postmaster级必须重启数据库才能生效比如max_connections。Sighup级改完reload就能用auto_createtblspcdir就是这种。Backend级现有连接不生效新连接自动加载新值。Session级当前会话直接改只对当前会话生效。参数还有优先级概念从高到低依次是会话级SET命令 ALTER DATABASE SET ALTER ROLE SET kingbase.auto.conf kingbase.conf 编译时默认值。-- 看参数从哪来的SELECTname,setting,sourceFROMsys_settingsWHEREnameauto_createtblspcdir;配置管理上我有几条建议。关键参数统一写入配置文件结合Git追踪变更历史。改参数先在测试环境验证生产环境无小事。动态参数优先用ALTER SYSTEM SET持久化写入kingbase.auto.conf比直接编辑kingbase.conf更安全也方便审计。关于auto_createtblspcdir的具体配置建议开发测试环境保持默认开启on追求便捷和效率生产环境看情况如果你对存储布局管控要求特别严格可以关掉保持可控可追溯。不过说实话我自己的习惯是开着只要权限管好就行。这个才是重点——容器化环境里咋整好了前面聊的都是传统部署场景下的东西。现在谁还裸机部署啊Docker和K8s才是主流。那问题来了——表空间的自动创建目录特性在容器化环境里到底好不好使先说结论好使但有几个坑你得知道。K8s里的PV/PVC和表空间KES在K8s上部署官方提供了Operator用的是StatefulSetPVC的模式。数据目录通过PVC挂载确保Pod重建后数据不丢。但表空间这玩意儿比较特殊——你需要额外的挂载点。# 一个典型的KES StatefulSet配置注意表空间的额外卷apiVersion:apps/v1kind:StatefulSetmetadata:name:kingbasespec:serviceName:kingbasereplicas:1template:spec:containers:-name:kingbaseimage:kingbase:v1volumeMounts:-name:datamountPath:/home/kingbase/userdata-name:tbs-hot# 热数据表空间挂载mountPath:/mnt/ssd/tbs-name:tbs-cold# 冷数据表空间挂载mountPath:/mnt/hdd/tbsvolumeClaimTemplates:-metadata:name:dataspec:accessModes:[ReadWriteOnce]storageClassName:managed-nfs-storageresources:requests:storage:50Gi-metadata:name:tbs-hotspec:accessModes:[ReadWriteOnce]storageClassName:ssd-storage# SSD存储类resources:requests:storage:100Gi-metadata:name:tbs-coldspec:accessModes:[ReadWriteOnce]storageClassName:hdd-storage# HDD存储类resources:requests:storage:500Gi关键点是auto_createtblspcdir开启的情况下你只需要确保PVC挂载到了正确的路径比如/mnt/ssd/tbs然后在KES里直接执行CREATE TABLESPACE就行了。不需要进容器手动mkdir数据库自动帮你把子目录建好。但这里有个大坑——PVC挂载的根目录属主问题。默认情况下K8s的PV挂载进来的时候目录属主可能是root。而KES容器里是用kingbase用户跑的所以你需要# Dockerfile里或者initContainer里处理权限 # 方式一在容器启动脚本里加 chown -R kingbase:kingbase /mnt/ssd/tbs /mnt/hdd/tbs chmod 700 /mnt/ssd/tbs /mnt/hdd/tbs或者用securityContext配置securityContext:fsGroup:1000# kingbase用户的gidrunAsUser:1000# kingbase用户的uid这样K8s在挂载PV的时候会自动把目录属主调整成kingbase用户配合auto_createtblspcdir就能做到挂载即用。S3兼容存储想都别想…呃也不是完全不行有人问我能不能把S3兼容接口的存储挂载过来当表空间用说实话这事儿挺邪门的。KES的表空间底层走的是POSIX文件系统接口要求目录操作是原子的、强一致的。而S3本质上是对象存储不支持POSIX语义。直接用S3当表空间路径不行。但是——可以通过S3兼容的文件系统网关比如S3FS、JuiceFS、Goofys把对象存储映射成本地目录。映射完之后对KES来说就是一个普通目录auto_createtblspcdir该怎么用还怎么用。# 用JuiceFS把S3映射为本地路径juicefsformat\--storages3\--buckethttps://my-bucket.s3.cn-north-1.amazonaws.com.cn\--access-key xxx\--secret-key xxx\redis://localhost/0\myjfs juicefsmount-dredis://localhost/0 /mnt/juicefs/tbs-archivechown-Rkingbase:kingbase /mnt/juicefs/tbs-archive-- 然后在KES里直接建表空间auto_createtblspcdir帮你建子目录CREATETABLESPACEtbs_archive LOCATION/mnt/juicefs/tbs-archive/data;不过要注意性能问题。对象存储的延迟比本地盘高一个数量级只适合冷数据归档场景。我之前试过把热数据表空间放S3FS上那个IO性能简直…整无语了查询慢了十倍不止。存储分层——这才是云原生的正确打开方式在云环境下表空间最核心的价值不是把数据放在不同的地方而是把不同访问特征的数据放在不同性能的存储介质上——也就是所谓的存储分层Tiered Storage。说实话这个思路不算新鲜Oracle玩了很多年了。但在云原生环境里它有了新的实现方式。热数据上SSD冷数据上HDD假设我们有个ERP系统数据访问特征差异很大——基础数据模块访问频繁交易数据量巨大且并发高财务报表历史数据多但访问少文件附件基本就是归档。传统做法是不同表空间指定不同挂载点。云原生环境下我们用不同的StorageClass来实现# SSD StorageClass - 给热数据用apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:ssd-storageprovisioner:disk.csi.alibabacloud.comparameters:type:cloud_essdperformanceLevel:PL1reclaimPolicy:RetainvolumeBindingMode:WaitForFirstConsumer---# HDD StorageClass - 给冷数据用apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:hdd-storageprovisioner:disk.csi.alibabacloud.comparameters:type:cloud_efficiencyreclaimPolicy:RetainvolumeBindingMode:WaitForFirstConsumer然后在KES里创建对应的表空间-- 热数据放SSDCREATETABLESPACEtbs_erp_base LOCATION/mnt/ssd/tbs/erp_base;CREATETABLESPACEtbs_erp_trade LOCATION/mnt/ssd/tbs/erp_trade;-- 交易索引单独一个表空间和基表分开减少IO争用CREATETABLESPACEtbs_erp_trade_idx LOCATION/mnt/ssd/tbs/erp_trade_idx;-- 冷数据放HDDCREATETABLESPACEtbs_erp_report LOCATION/mnt/hdd/tbs/erp_report;CREATETABLESPACEtbs_erp_archive LOCATION/mnt/hdd/tbs/erp_archive;建表的时候指定表空间-- 热数据表 - 基表放SSD索引也放SSD但分开表空间CREATETABLEtrade_orders(id BIGSERIAL,order_noVARCHAR(32)NOTNULL,customer_idINT,amountNUMERIC(12,2),statusVARCHAR(16),created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP)TABLESPACEtbs_erp_trade;CREATEINDEXidx_orders_noONtrade_orders(order_no)TABLESPACEtbs_erp_trade_idx;-- 冷数据表 - 放HDD就行CREATETABLEfinance_report_2024(id BIGSERIAL,report_typeVARCHAR(32),dataJSONB,generated_atTIMESTAMP)TABLESPACEtbs_erp_report;临时表空间也别忘了很多人忽略临时表空间但在高并发排序、大结果集hash join的场景下临时表空间的IO压力可能比主表空间还大。独立的临时表空间能避免临时IO冲击业务数据盘-- 临时表空间也放SSD上CREATETABLESPACEtbs_temp LOCATION/mnt/ssd/tbs/temp;-- 设置默认临时表空间SETtemp_tablespacestbs_temp;-- 或者持久化ALTERSYSTEMSETtemp_tablespacestbs_temp;SELECTpg_reload_conf();动态扩容怎么办云环境最大的优势就是弹性。表空间对应的PVC空间不够了不需要停库直接扩容PVC# 扩容PVC前提是StorageClass支持动态扩容kubectl patch pvc tbs-hot-kingbase-0\-p{spec:{resources:{requests:{storage:200Gi}}}}KES这边不需要任何操作文件系统扩容后表空间自动就能用上新的空间。这个配合auto_createtblspcdir的好处是——新建表空间不需要进容器操作文件系统一条SQL搞定运维复杂度直线下降。版本差异这事儿真的坑爹前面提了一嘴这里展开说。V8默认offV9默认on这个差异不是随便改着玩的。V8的时候保持的是传统行为——你得自己建目录。这是为了和当时的生态兼容很多老用户的自动化脚本都是基于先建目录再建表空间的流程写的。V9改成了默认on主要是因为越来越多的用户从Oracle迁移过来Oracle的习惯就是建表空间不用管目录。为了降低迁移门槛默认行为改了。所以如果你的环境是V8想用这个特性得手动开-- V8环境默认off手动开启ALTERSYSTEMSETauto_createtblspcdiron;SELECTpg_reload_conf();如果你的运维手册和部署脚本是按V8写的迁移到V9的时候要注意这个差异。我之前就见过一个项目测试环境用V9一切正常上了生产发现是V8…创建表空间的脚本全报错。看了眼表凌晨两点半服了。超级用户权限管理——别给多了表空间创建和删除只能由超级用户执行这是KES的安全设计。但超级用户权限大了风险也大。生产环境里我的建议是超级用户账号越少越好一般保留两三个就够。一个给DBA日常维护一个给应用连接池。其他的应用系统账号别给superuser权限。-- 查看所有超级用户看看有没有不该有的SELECTrolnameFROMpg_rolesWHERErolsupertrue;-- 创建应用用户的时候只给需要的权限CREATEUSERapp_readonlyWITHPASSWORDxxx;GRANTUSAGEONSCHEMApublicTOapp_readonly;GRANTSELECTONALLTABLESINSCHEMApublicTOapp_readonly;-- 如果需要在特定表空间建表授权CREATE就行CREATEUSERapp_devWITHPASSWORDxxx;GRANTCREATEONTABLESPACEtbs_erp_tradeTOapp_dev;定期审查权限配置。看看有没有账号长期不用但权限还在有没有权限被滥用的情况。这个说起来简单但真正做到的团队真不多。踩坑实录——属主问题是最阴的我自己的血泪教训有一次在容器环境里创建表空间死活不成功。日志里写着属主不对但我在容器里ls -la看了又看属主明明就是kingbase啊。折腾了好久头发都快揪秃了。最后发现——是PVC挂载的那个根目录的属主不对。/mnt/ssd/tbs本身是root属主虽然我让KES自动创建/mnt/ssd/tbs/erp_trade但父目录/mnt/ssd/tbs不是kingbase用户的所以自动创建失败。解决方法很简单在容器启动脚本里加一行# initContainer或者在entrypoint里mkdir-p/mnt/ssd/tbs /mnt/hdd/tbschown-Rkingbase:kingbase /mnt/ssd/tbs /mnt/hdd/tbschmod700/mnt/ssd/tbs /mnt/hdd/tbs或者用K8s的securityContext.fsGroup让K8s自动处理挂载目录的权限。另外一个坑如果你用NFS挂载的目录要注意NFS的root_squash配置。NFS默认会把root用户映射成nobody如果kingbase用户的uid在NFS服务端和客户端不一致也会出现属主问题。这个坑我也是踩了才知道…# NFS挂载的时候注意sync/async和no_root_squashmount-tnfs nfs-server:/data/tbs /mnt/nfs/tbs\-orw,sync,hard,no_root_squash一条SQL完成的快感说实话auto_createtblspcdir开了之后创建表空间真的变成了一条SQL的事。在容器化环境里配合PVC和StorageClass整个流程是这样的在K8s里声明PVC绑定不同StorageClass在容器配置里把PVC挂到指定路径在KES里一条SQL建表空间建表指定表空间没有手动建目录没有权限问题没有属主不对的报错。这才是云原生的体验。-- 一气呵成CREATETABLESPACEtbs_hot LOCATION/mnt/ssd/tbs/hot;CREATETABLESPACEtbs_cold LOCATION/mnt/hdd/tbs/cold;CREATETABLESPACEtbs_temp LOCATION/mnt/ssd/tbs/temp;-- 热数据CREATETABLEuser_activity(id BIGSERIAL,user_idINT,actionVARCHAR(64),occurred_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP)TABLESPACEtbs_hot;-- 冷归档CREATETABLEuser_activity_archive(LIKEuser_activity INCLUDINGALL)TABLESPACEtbs_cold;最后啰嗦两句一个自动建目录的小功能聊了这么多值不值我觉得值。因为表面上看它只是帮你省了一步mkdir但往深了挖它引出了表空间的安全约束体系、IO分离的调优思路、磁盘规划的运维规范、GUC参数管理的底层机制、还有云原生存储的弹性架构。这些知识串在一起才是一套完整的数据库存储管理能力。几条最实用的建议收尾auto_createtblspcdir保持默认开启省心。表空间规划的核心是IO分离——数据和索引分开、冷热分开、临时操作单独隔离。目录权限用700属主必须一致定期检查。改GUC参数用ALTER SYSTEM SET不要直接编辑kingbase.conf。云环境里配合PVC和StorageClass把存储分层做起来。NFS环境注意属主映射容器环境注意fsGroup配置。