ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oracle 19c多租户新增PDB全流程:从建库到扩容的实操与避坑

Oracle 19c多租户新增PDB全流程:从建库到扩容的实操与避坑 上周接到一个需求新项目要一套独立数据库数据隔离是硬性要求。我手头正好有一台跑在Linux上的Oracle 19cCDB已经在线运行里面还挂着两个正在用的PDB。这种场景下显然没必要再装一套完整实例——Oracle 19c多租户架构本身就是为这种情况设计的在已有CDB里新增一个PDB实例资源共享数据字典和应用数据完全隔离连接串独立备份恢复也是各自独立的。整个流程沿着“建PDB库 → 建表空间 → 建用户指定表空间 → 表空间扩容”这条链路走下来顺利的话半小时内就能把一套新数据库环境交付出去。但每一步都有细节尤其是刚从非多租户架构转过来的DBA容易在容器切换、文件路径、Quota这些地方栽跟头。这篇文章就把我这次操作的全过程、用到的命令、以及遇到的坑完整记录下来。1. 动手前先确认三件事CDB状态、磁盘空间和PDB规划在已有CDB里新增PDB最大的风险不是命令不会写而是对当前环境心里没数。CDB还挂着两个生产PDB一个不小心就会影响它们所以我的习惯是动手之前先做一轮状态检查和资源评估。1.1 三句话确认CDB与PDB$SEED状态先用系统管理员身份登录CDB执行下面几条最基本的查询sqlplus / as sysdba SELECT name, cdb, open_mode FROM v$database; SELECT con_id, name, open_mode FROM v$pdbs; SELECT pdb_name, status, con_id FROM cdb_pdbs;解释一下输出怎么看v$database里cdb列如果是YES说明当前库就是以多租户方式运行的后面所有操作才成立如果显示NO那就是传统非CDB架构整套方案都要换掉得先把库转成CDB或者用DBCA重建。v$pdbs会列出当前CDB里的所有容器包括CDB$ROOT、PDB$SEED和已经创建的PDB。正常情况下已有PDB处于READ WRITEPDB$SEED显示MOUNTED是正常的——seed本质是一个只读模板不是让你连上去干活的。cdb_pdbs.status显示NORMAL表示PDB状态健康。这里想提醒一个很多人忽略的点即使你在CDB root里执行CREATE USER只要用户名不带C##前缀19c会直接报ORA-65096。这就是多租户和传统库最直观的差异。所以后续所有针对业务的操作都要明确“我现在在哪个容器里”。1.2 磁盘和参数资源要提前算清楚CDB是共享同一个实例的新增PDB不会单独分配SGA、PGA只是在这个实例上多跑一套数据。所以资源规划主要看两件事磁盘和连接/文件数。先看磁盘df -h /u01/app/oracle/oradata这一步特别重要。很多人建PDB时只顾写命令结果数据文件把磁盘撑爆。我再补一句Oracle在创建PDB时会把seed里的数据文件复制一份到新PDB目录这部分空间是立即占用的不是后续增长才占所以预估空间时要把 seed 文件大小也算进去。再确认几个参数show parameter db_files; show parameter processes; show parameter sessions;db_files决定整个CDB能容纳的数据文件总数。PDB一多、每个PDB再挂好几个表空间文件数涨得很快。如果这个参数是默认的200左右建议提前调大免得将来加数据文件时碰到数量上限。注意这个参数是实例级别的改动要重启才生效所以要提前规划不要等生产环境扩容时才想起来。顺手做一张规划表这是我在操作前必填的清单规划项用途说明本次实例取值PDB名称一眼看出归属别起无关名字app_pdbPDB管理员账号管理PDB内部对象的本地管理员app_admin业务账号应用连接数据库用的账号app_user默认表空间业务数据最终落盘位置app_ts临时表空间排序、hash等临时段使用tempPDB自带数据文件路径最好跟随PDB目录/u01/app/oracle/oradata/ORCLCDB/app_pdb/初始大小策略预留近期增长空间512M起步autoextend开上限8G把这张表填完后面每一步命令里的变量都可以直接对号入座不容易写错。2. 从PDB$SEED创建新PDB一条命令和它的两个关键点创建PDB主要有三种方式从PDB$SEED直接创建全新数据库最常用克隆已有PDB适合复制一套现成环境当测试库unplug/plug方式把PDB从别的CDB插进来适合迁移场景。这次新项目是全新业务没有现成环境可复制所以最合适的是从seed创建。2.1 从seed建还是克隆已有PDB先分清场景这部分值得单独说一下因为很多人一听到“复制PDB”就问能不能直接克隆。我的建议是如果业务是全新的表结构、数据都要从头建那就老老实实从seed创建干净利落。如果是要快速拉一套跟现有环境一模一样的数据比如测试环境复刻生产本地克隆更好用。克隆时源PDB必须处于OPEN READ ONLY或者关闭状态克隆完成再切回去。如果是整库迁移到新CDBunplug/plug更合适。做一个简单对照方式适用场景源状态要求复杂度从PDB$SEED创建全新项目、空库seed自动就绪低克隆已有PDB复制环境、测试库OPEN READ ONLY或关闭中unplug/plug跨CDB迁移源端关闭后拔出中高2.2 实操命令FILE_NAME_CONVERT、ADMIN USER与STORAGE限制确认环境没问题、方案也定了执行创建命令CREATE PLUGGABLE DATABASE app_pdb ADMIN USER app_admin IDENTIFIED BY Admin#123 FILE_NAME_CONVERT ( /u01/app/oracle/oradata/ORCLCDB/pdbseed, /u01/app/oracle/oradata/ORCLCDB/app_pdb ) STORAGE (MAXSIZE 10G) PATH_PREFIX /u01/app/oracle/oradata/ORCLCDB/app_pdb/;几个关键点我逐个说清楚FILE_NAME_CONVERT是文件路径映射规则。它告诉Oracleseed目录下的所有数据文件都复制到新PDB对应的目录里。如果你不指定Oracle会尝试自动生成但文件可能分散在别的地方后面管理很头疼。所以我的建议是永远显式指定转换规则做不做OMF都先把这个写上。ADMIN USER是PDB内部的本地管理员。这个账号可以登录PDB做管理但它是PDB本地用户没有CDB root的权限跟sys这种common user不是一个层级。STORAGE (MAXSIZE 10G)是可选的但建议加上。它给这个PDB设一个总存储上限。没有上限的话一个失控的报表查询就可能把磁盘填满。10G只是示例按需调整。PATH_PREFIX和FILE_NAME_CONVERT搭配使用限制这个PDB所有文件都放在同一个目录下对后续备份策略和巡检都比较友好。命令执行成功后查一下SELECT name, open_mode FROM v$pdbs WHERE name APP_PDB;这时新PDB通常是MOUNTED状态因为创建本身还没把它打开。2.3 打开PDB并Save State别等重启后才后悔新PDB创建完之后不能直接用还得打开并保存启动状态ALTER PLUGGABLE DATABASE app_pdb OPEN; ALTER PLUGGABLE DATABASE app_pdb SAVE STATE;SAVE STATE这条命令是我强烈建议每一次创建PDB后都执行的。它的作用是告诉CDB以后整个实例重启时自动把这个PDB恢复到当前打开状态。如果不执行CDB一旦重启所有PDB默认回到MOUNTED应用连接直接失败。这个坑真不是一次两次在凌晨重启后遇到了——人还没醒告警先响。如果CDB里PDB已经很多我习惯直接ALTER PLUGGABLE DATABASE ALL SAVE STATE;一次性把现有PDB的启动状态都固化下来省得一个个写。最后再执行SHOW PDBS看一眼确认新PDB已经是READ WRITE状态正常这一步才算完。3. 建表空间前先切换容器文件路径与段管理方式的选择PDB开起来了下一个动作是建表空间。但在建表空间之前有一个最常见也最伤人的操作顺序问题容器切换。3.1 容器切错了表空间就建到CDB根里了从CDB root登录后当前会话默认还是在CDB$ROOT容器里哪怕你明明感觉自己已经“连到了新PDB”。如果你没切容器就直接执行CREATE TABLESPACE app_ts ...;这条表空间会建到CDB$ROOT下面也就是整个CDB共享的那一层。这在多租户架构里是大忌轻则表空间混乱重则影响其他PDB的字典空间。所以建任何业务对象之前第一件事永远是ALTER SESSION SET CONTAINER app_pdb; SELECT SYS_CONTEXT(USERENV, CON_NAME) FROM dual;第二条查询会返回当前容器名用来确认切换是否成功。我习惯把这条放进所有脚本模板的开头防止后面操作在错误的容器里执行。另外多租户下PDB里不能创建UNDO表空间——UNDO是整个CDB共享的不用也不应该自己建。如果需要单独的表空间就建普通业务表空间临时表空间PDB自带了一套TEMP绝大多数情况下直接用默认的就行。3.2 数据文件路径规划与表空间参数取舍确认在正确的容器里之后创建业务表空间CREATE TABLESPACE app_ts DATAFILE /u01/app/oracle/oradata/ORCLCDB/app_pdb/app_ts01.dbf SIZE 512M AUTOEXTEND ON NEXT 64M MAXSIZE 8G EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;先说路径。我建议数据文件放在PDB自己的目录下也就是xxx/app_pdb/这种结构。这样将来做RMAN备份、排查文件归属、甚至迁移PDB都能一眼认出这套文件属于哪个容器非常省心。这里有个真实的坑Oracle不会替你创建不存在的子目录。如果你在路径里写了一个没建过的目录执行时会直接报错。所以要么用已经存在的目录要么先mkdir -p建好目录再执行。别让这种小事卡住。再说参数。EXTENT MANAGEMENT LOCAL是本地管理表空间这个在19c基本是默认共识了不用考虑字典管理表空间。关键是AUTOALLOCATE和UNIFORM SIZE的选择AUTOALLOCATE让Oracle根据段的大小自动选择合适的区大小适合OLTP场景表大小不一、增长模式不可预测。UNIFORM SIZE 1M所有区固定大小适合大量小对象或者某些特殊运维场景空间利用率更可控但需要你比较了解对象的增长特征。绝大多数业务系统直接用AUTOALLOCATE是最省事且合理的。SEGMENT SPACE MANAGEMENT AUTO同样是现代Oracle的默认选项段内的空闲空间管理交给位图自动完成比老式MANUAL手工管理链表的方式高效很多不需要特殊理由保持默认就好。关于AUTOEXTEND ON NEXT 64M MAXSIZE 8G我单独解释一下。我的建议很简单自动扩展要打开但必须设上限。开自动扩展是为了避免每次空间不足时手工介入设上限是为了防止某个异常SQL把磁盘写满。NEXT设置多大也讲究太小频繁扩展消耗性能太大容易浪费空间。64M到128M对大多数OLTP系统是合理的起点。3.3 建完表空间立刻检查文件和大小建表空间这种DDL执行成功不代表一切正常。我习惯马上查一遍文件信息SELECT tablespace_name, file_name, bytes/1024/1024 size_mb, autoextensible, maxbytes/1024/1024 max_mb, status FROM dba_data_files WHERE tablespace_name APP_TS;重点看这几个字段文件路径是否落在预期目录、autoextensible是不是YES、max_mb是不是设置的8G上限、status是不是AVAILABLE。全部正常再继续往下走。这一步花不了一分钟但能避免很多后续排查的麻烦。4. 创建用户并指定表空间三个高频坑一次性全避开表空间就绪接着创建业务用户。表面上看就是一条CREATE USER但这里有三个高频坑我一个一个复盘。4.1 完整建用户语句默认表空间、临时表空间、Quota一个都不能少先看完整语句ALTER SESSION SET CONTAINER app_pdb; CREATE USER app_user IDENTIFIED BY App#2024 DEFAULT TABLESPACE app_ts TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON app_ts PROFILE default;第一坑DEFAULT TABLESPACE不指定。新建的PDB里默认没有USERS表空间如果创建用户时不指定默认表空间用户的默认表空间很可能会落到SYSTEM上。应用产生的所有段对象全部写到SYSTEM表空间这对数据库的危害不用我多说。所以必须显式指定业务表空间。第二坑TEMPORARY TABLESPACE不指定。不指定会沿用数据库默认临时表空间一般也是TEMP问题不大但显式写出来更严谨尤其是将来PDB里如果有多个临时表空间、想给不同用户区分临时空间时这一项就必须显式控制了。第三坑QUOTA没给。这个坑在“4.3”里专门讲因为它的报错特别容易误导人。还有个细节密码为什么要用双引号包起来因为Oracle的密码如果不加双引号会默认转成大写。你设的App#2024可能实际变成了APP#2024应用层连接串里对不上半天查不出原因。用双引号写密码Oracle会严格按大小写处理。这里还要注意一下用户命名。在PDB中创建的业务用户是本地用户不需要也不能加C##前缀。C##前缀只用于CDB root下创建的common user。如果你在CDB root下写一条不带C##的CREATE USER19c会报ORA-65096同样地带着C##在PDB里建用户也不合适会产生一个你未必想要的全局账号。正确做法非常明确业务本地用户就在PDB容器里创建。4.2 权限授予ConnectResource够用但别滥用DBA用户创建好之后权限授予是下一步。我的推荐起步是GRANT CONNECT, RESOURCE TO app_user;CONNECT提供最基本的连接能力RESOURCE提供创建表、序列、视图等常用对象的能力大多数内部业务系统这个组合就够了。如果应用还涉及外部表、调优包、特定包的执行再按需要补GRANT EXECUTE ON ...或者对应的系统权限。关键是除非是开发人员的个人管理账号否则不要直接给应用账号DBA角色。DBA权限在19c里同时在CDB和所有PDB生效它的系统权限列表太大一旦应用被注入或者账号泄密损失范围是整个数据库而不是一个PDB。4.3 两个报错复盘ORA-01950和容器方向错误第一个报错ORA-01950: no privileges on tablespace APP_TS。这个报错特别有迷惑性。表面看是权限问题实际上80%的情况是用户在该表空间的Quota配额没给。前面CREATE USER时如果没有写QUOTA UNLIMITED ON app_ts用户在这个表空间上没有任何空间份额自然一块数据都插不进去。修复其实简单ALTER USER app_user QUOTA UNLIMITED ON app_ts;如果你想把应用用户在某个表空间上限制得更细也可以给具体大小比如QUOTA 2G ON app_ts。但注意如果用户被授了UNLIMITED TABLESPACE系统权限比如有人图省事给了DBA角色那么不管Quota怎么设置都等于没有限制。所以最小权限原则下Quota要认真管理。第二个报错ORA-65096: invalid common user or role name。这个报错出现在你人在CDB root却想创建不带C##前缀的用户时。报错信息长得很奇怪网上搜索一堆解释但本质就是容器方向错了。解决方法是先切回目标PDB再执行创建。如果你是连接串登录的确认连接字符串里服务名是PDB服务名而不是CDB的。5. 表空间扩容Resize、加数据文件、Autoextend怎么用表空间建好了用户在用了。但数据库这东西没人敢保证空间永远够用扩容是绕不开的日常操作。这次我把扩容的几种姿势一次讲透。5.1 先看使用率dba_tablespace_usage_metrics的局限扩容之前先明确要不要扩、扩到多少。很多人上来就写SQL查dba_tablespace_usage_metricsSELECT tablespace_name, used_space, tablespace_size, used_percent FROM dba_tablespace_usage_metrics WHERE tablespace_name APP_TS;这个视图用起来确实方便但它有一个关键局限tablespace_size用的是当前所有数据文件已经分配的大小而不是MAXSIZE上限。也就是说如果一个表空间的数据文件配了AUTOEXTEND ON MAXSIZE 8G而现在刚分配了512M使用率可能只有10%但磁盘空间理论上还能膨胀到8G。这时候看使用率可能低估风险。所以我的建议是判断扩容需求看两个指标结合一是当前已用空间二是所有文件的MAXSIZE上限。前者决定“现在够不够用”后者决定“未来还能撑多久”。对应的查询SELECT tablespace_name, file_name, bytes/1024/1024 current_mb, maxbytes/1024/1024 max_mb, autoextensible FROM dba_data_files WHERE tablespace_name APP_TS;再配合SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024) free_mb FROM dba_free_space WHERE tablespace_name APP_TS GROUP BY tablespace_name;结合起来就能比较准确地判断当前的剩余空间了。5.2 三种扩容方式的具体命令与取舍确定了要扩容通常有三条路。第一条直接RESIZE现有数据文件。适用于文件在创建时规格偏低、磁盘上还有足够连续空间、并且还没到文件数量上限的场景。ALTER DATABASE DATAFILE /u01/app/oracle/oradata/ORCLCDB/app_pdb/app_ts01.dbf RESIZE 2G;注意RESIZE不能小于该数据文件已使用的空间否则直接报ORA-03297: file contains used data beyond requested RESIZE value。所以扩容前先查一下文件的实际使用量留出至少20%的余量。第二条给表空间增加一个新的数据文件。适用于原文件MAXSIZE已经快到顶、想进一步提升容量同时分散I/O的场景。ALTER TABLESPACE app_ts ADD DATAFILE /u01/app/oracle/oradata/ORCLCDB/app_pdb/app_ts02.dbf SIZE 1G AUTOEXTEND ON NEXT 128M MAXSIZE 16G;加文件的好处是可以在多个文件之间分摊写入缺点是文件数量增加、管理成本上升而且如果有文件损坏恢复时需要处理的对象也更多。所以“能通过resize解决就优先resize”是我个人的习惯。第三条调整AUTOEXTEND的MAXSIZE上限。适用于当前文件还有增长空间只是上限设小了。ALTER DATABASE DATAFILE /u01/app/oracle/oradata/ORCLCDB/app_pdb/app_ts01.dbf AUTOEXTEND ON NEXT 64M MAXSIZE 16G;我个人对自动扩展的态度是生产环境可以开但一定要设上限。上限的作用是防止失控SQL把磁盘耗尽导致整个CDB所有PDB一起出问题——因为磁盘是共享的。三种方式对照方式适用场景关键命令注意点RESIZE单文件扩容ALTER DATABASE DATAFILE ... RESIZE不能小于已用空间ADD DATAFILE分散I/O、原文件到顶ALTER TABLESPACE ... ADD DATAFILE文件数量与备份范围增加AUTOEXTEND上限调整文件还有空间ALTER DATABASE DATAFILE ... AUTOEXTEND必须设MAXSIZE防失控5.3 扩容后的两个验证动作扩容命令执行成功不等于结束。我每次扩完都做两件事。第一再查一次dba_data_files确认新文件或者resize后的文件状态是AVAILABLE大小和预期一致。第二检查操作系统磁盘余量。这个听起来蠢但真有人加完文件后现场发现磁盘早就满了虽然DDL执行成功但后续写操作全在报警。这时候看df -h比看数据库内部视图更直接。6. 交付后验证和运维习惯让新增PDB稳稳上线前面几步做完数据库环境已经成型了。但交付前还需要做最后的连接验证以及调整后续运维习惯。6.1 连接验证与监听服务名PDB创建后Oracle的监听会自动注册PDB对应的服务名通常就是PDB名称可能会有域名后缀。先用命令确认lsnrctl services看输出里有没有app_pdb相关的service。如果没看到多半是动态注册还没刷新可以等一会儿或者手动触发ALTER SYSTEM REGISTER;然后从应用层视角连一次模拟真实连接sqlplus app_user/App#2024//localhost:1521/app_pdb输入几条查询比如SELECT sys_context(USERENV, CON_NAME) FROM dual;确认返回的是APP_PDB而不是CDB root。这一步能同时验证监听、服务名、用户密码、默认表空间几个环节省得后面应用接入时出问题再层层排查。6.2 固定脚本模板与监控阈值调整这套流程我重复过太多次现在已经固化成脚本模板了。每个新项目来了只需要改四个变量PDB名称、表空间名称、用户名、路径前缀跑一遍基本不会出错。模板里固定包含环境检查、创建PDB、开PDB并保存状态、切容器、建表空间、建用户授权、扩容预案和最终的连接验证每一段都有明确的输出检查点。另外新PDB上线后一定要记得把它纳入日常监控和备份策略。RMAN方面多租户下可以对PDB单独做备份不需要整个CDB一把梭监控方面把app_ts表空间使用率、v$pdbs.open_mode、磁盘剩余空间这几个指标加进告警阈值。我在实际操作中发现很多环境出了问题不是没监控而是告警阈值设得太宽比如表空间到95%才告警而应用在90%时已经开始大量报错。建议根据业务写入速度把阈值设得保守一些。最后说一句个人体会多租户不是把多个库塞进同一个实例就完事了它真正考验的是你对“共享什么、隔离什么”的理解。资源是共享的所以磁盘、内存、文件数都要全局看数据和权限是隔离的所以容器切换、本地用户、Quota一样都不能马虎。把这套流程理顺以后新项目加数据库就是一件十分钟左右的事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进