一提起Oracle多租户容器数据库CDB/PDB很多人第一反应是这不就是把多个库塞进一个大库的壳里嘛。真用起来才发现里面的门道比想象的深连接串怎么配、克隆为什么要先设只读、RMAN恢复时为什么不能随便对某个PDB做不完全恢复、资源管理计划到底管住了什么——这些问题只看概念图是看不明白的实操一遍才会真正理解。这篇文章我把从创建CDB、建第一个PDB到日常管理、克隆迁移、备份恢复、资源隔离这一条线上容易踩的坑和值得记住的细节整理出来。适合刚接触多租户架构的人也适合那些已经用了一段时间但总在某几个报错上反复折腾的同路人。1. 为什么需要多租户架构一堆独立库带来的混乱1.1 传统模式下的资源浪费与管理成本早些年的数据库部署方式基本是一套业务一套独立实例A系统的库占一台机器B系统的库再占一台机器。每个库都要吃一块专属的内存、SGA、一组后台进程哪怕业务量很小机器配置也得按最大峰值去留更麻烦的是补丁和版本升级要一台一台打十套库就是十份重复劳动。多租户架构把一个实例对应一套库的模式改成了一套CDB容器数据库里装多个PDB可插拔数据库的模式。CDB是壳PDB是真正面向业务的数据容器。对于业务应用来说PDB仍然是一个完整独立的数据库有自己的数据字典、用户、表空间、权限体系对于DBA来说底层只需要维护一个数据库实例资源可以共享补丁只需打一次备份也能按PDB粒度走。1.2 CDB、PDB和根容器之间的关系CDB里除了PDB还有一个Root容器CDB$ROOT它保存了SPFILE、控制文件、UNDO等实例级元数据以及Oracle提供的字典表和公共对象。PDB则像一个个租户它们共享CDB实例的后台进程和SGA但在逻辑层面彼此隔离。我经常用公寓来打比方CDB是整栋楼楼里的水电总闸、电梯、公共走廊是Root每间房子是PDB住户只关心自己屋里的装修和家具不用管大楼的管线。如果大楼水管维修整栋楼都会受影响这就是为什么CDB级操作比如重启实例、改控制文件会影响所有PDB而房间里怎么折腾只要不破坏承重墙邻居不会知道。1.3 多租户带来的实际收益和取舍收益最直观的几点整合成本一台服务器跑十几个PDB机器数量和许可成本都下降。维护提速补丁、升级针对CDB做一遍所有PDB同时受益。快速复制PDB克隆可以秒级生成开发库配合快照克隆能做到分钟级交付。备份灵活可以只备份某一个PDB不完全恢复也能限定在PDB范围。但代价也存在同一CDB里的PDB共用一套实例资源一个吵闹的邻居可能吃掉大量CPU和IOCDB级别的故障比如实例崩溃会影响内部所有PDB。所以多租户不是白拿好处资源管理和监控的功夫不能省。2. 从创建到连接手把手拉起第一个CDB和PDB2.1 创建CDB时的关键选项安装Oracle 19c时选择创建CDB还是非CDB是第一个分水岭。新建实例时勾选Create as Container Database指定CDB名字其他选项包括EMEnterprise Manager建议开启多租户的管理比以前复杂图形界面看PDB状态更方便。字符集这是后面排查问题最头疼的点。CDB的字符集最好与你预期的所有PDB兼容尽量用AL32UTF8后面克隆PDB时对字符集的一致性要求比较高。本地UNDO从12cR2开始支持每个PDB单独配UNDO表空间这个选项建议开启。它带来的好处是PDB故障时可以独立清理UNDO不至于影响CDB里其他PDB。安装完成后可以查询一下基本信息show con_name; show con_id;不出意外会看到CON_NAME为CDB$ROOTCON_ID为1。2.2 用种子PDB创建第一个业务库CDB创建好之后里面会有一个种子PDBPDB$SEED它是所有新PDB的模板。创建新PDB最标准的做法就是从种子克隆create pluggable database salespdb admin user sales_admin identified by your_password file_name_convert (/u01/oradata/orcl/pdbseed, /u01/oradata/orcl/salespdb) path_prefix /u01/oradata/orcl/salespdb;这里两个关键点admin user创建PDB管理员用户后续在这个PDB里创建业务用户、表空间都要靠它。file_name_convert把种子PDB的数据文件路径替换成新PDB的路径。如果CDB的OMFOracle Managed Files配置正确有时可以省略但建议还是显式写清楚路径混乱是管理灾难。创建完成后PDB的状态是MOUNTED并不能直接连接。需要打开它alter pluggable database salespdb open; alter pluggable database salespdb save state;save state这条命令容易忽略。它的作用是让CDB下次启动时自动把这个PDB恢复到关闭前的状态。如果不加CDB重启后PDB默认是MOUNTED业务连接串会直接报服务不存在。查看PDB状态select name, open_mode, con_id from v$pdbs;2.3 监听注册与service_name连接配置最容易翻车刚玩多租户的人十有八九在连接上栽过跟头。核心原因是PDB有自己的service_nameCDB也有自己的service_name同一个监听端口上挂了好几个服务。一个典型的tnsnames.ora配置SALESPDB (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST dbserver01)(PORT 1521)) (CONNECT_DATA (SERVICE_NAME salespdb) ) )注意这里SERVICE_NAME填的是PDB的名字salespdb不是CDB名orcl。很多人在命令行里用sqlplus system/oracle//host:1521/salespdb提示密码错误或ORA-01017其实不一定是密码问题很可能连的是CDB而不是PDB。还有一点要特别提醒想通过sys登录某个PDB有两种方式。一种是用PDB的service_name连接后输入sys/pwdsalespdb as sysdba另一种是连接到CDB后执行alter session set containersalespdb。很多时候应用的连接账号密码完全正确但sysdba身份只能从CDB管理PDB我吃过好几次类似教训。2.4 验证连接与常见报错创建完成后推荐先在命令行验证连接sqlplus sales_admin/your_password//dbhost:1521/salespdb如果报ORA-12514先检查监听状态lsnrctl status确认PDB的service_name是否出现在服务列表中。如果没出现执行alter system register强制注册或者等监听动态注册。这类问题绝大多数不是配置写错而是监听还没拿到最新服务名。3. PDB的日常管理切换、启停、表空间与用户隔离3.1 在不同容器之间切换日常管理PDB最核心的一条命令是切换容器alter session set container salespdb; show con_name;在CDB中执行这条命令后当前会话就进入salespdb。注意多数PDB级管理操作比如创建用户、表空间都需要先进到对应PDB再执行。CDB$ROOT里不应该创建业务对象虽然Oracle不强制但这是一种好习惯。如果嫌每次手动切换麻烦可以给每个PDB配一个专用连接串直接连过去再执行DDL。两种方式没有本质区别只是习惯问题。我比较推荐管理脚本里先显式声明容器避免脚本跑错库造成误操作。3.2 每个PDB是否需要独立UNDO和TEMP多租户环境下TEMP表空间默认每个PDB自己有一个临时表空间UNDO则要看安装时是否启用了本地UNDO启用了本地UNDO后每个PDB都有独立的UNDO表空间。查询PDB的UNDO和TEMPselect tablespace_name, contents from dba_tablespaces; -- 在对应PDB中执行独立UNDO意味着如果一个PDB里有长事务UNDO膨胀影响能控制在单个PDB内。但如果你用的是共享UNDO模式12cR1遗留场景一个PDB的大事务可能把CDB级UNDO撑爆那影响就是全局的。所以新版本环境下能启用本地UNDO就启用。3.3 在PDB中管理表空间与用户进入PDB后创建表空间和用户的方式与非CDB几乎一样create tablespace salests datafile /u01/oradata/orcl/salespdb/salests01.dbf size 2g autoextend on next 512m maxsize 32g; create user app_sales identified by app_pwd default tablespace salests quota unlimited on salests; grant connect, resource, create session to app_sales;需要留意的是PDB中的用户是隔离的。A用户在所有PDB里完全相同吗不是。每个PDB有自己独立的用户命名空间所以salespdb里的app_sales和anotherpdb里的app_sales是两个不同的用户。这在迁移PDB时更容易体现拔插件PDB到另一台CDB原来PDB里的用户直接跟着走不受新CDB公共用户影响。3.4 启动关闭save state 和 all 操作管理多个PDB时批量操作命令能省很多时间alter pluggable database all open; alter pluggable database all close immediate;关闭PDB时建议用immediate还是normal和普通实例类似的逻辑开发测试环境无所谓生产环境要尽量先通知应用端切换流量再执行关闭避免会话被强行中断。这里有个小坑CDB重启后如果某个PDB没有save state它会停留在MOUNTED状态业务报警马上就会来。所以新创建PDB后save state应该写成固定操作步骤之一。4. PDB克隆与迁移本地克隆、远程克隆和拔插玩法4.1 本地克隆快速生成一套开发库多租户最令DBA兴奋的功能之一就是PDB克隆。想在几秒内生成一套和生产结构一样的开发库-- 源PDB需要处于open read only alter pluggable database salespdb open read only; create pluggable database devpdb from salespdb file_name_convert (/u01/oradata/orcl/salespdb, /u01/oradata/orcl/devpdb); alter pluggable database devpdb open;克隆的原理是基于文件的拷贝所以速度很快但前提是源PDB必须是open read only状态。很多人第一次执行会报ORA-65081就是因为源库不是只读打开的。4.2 远程克隆跨CDB复制PDB远程克隆适用于把某台CDB里的PDB克隆到另一台CDB中。前提条件源PDBopen read only。目标CDB和源CDB都开启了本地UNDO。需要有数据库链接dblink连接源库且dblink用户有权限读取源PDB的数据字典通常要求有CREATE PLUGGABLE DATABASE权限。-- 在目标CDB中执行 create pluggable database remote_dev from salespdblink_to_source file_name_convert (/u01/oradata/orcl/salespdb, /u01/oradata/orcl/remote_dev);远程克隆适合两地环境同步比如从生产CDB克隆到灾备CDB做演练或者从生产克隆到预发环境。但在生产上执行前一定要确认网络延迟和传输时间数据量大的时候克隆任务跑很久是正常的。4.3 拔插PDB把房间搬到另一栋楼拔插unplug/plug是真正的PDB迁移方式。它不像克隆会生成一个对照副本而是把PDB从原CDB中摘下来再挂到目标CDB上。拔出的步骤alter pluggable database salespdb close; alter pluggable database salespdb unplug into /backup/salespdb.xml; drop pluggable database salespdb keep datafiles;注意keep datafiles它告诉Oracle保留数据文件不删除这样你才能把文件拷到目标服务器。然后到目标CDB上执行create pluggable database salespdb using /backup/salespdb.xml file_name_convert (/u01/oradata/orcl/salespdb, /u01/oradata/orcl/salespdb);拔插最需要注意版本兼容性源CDB和目标CDB的版本必须满足一定条件低版本CDB拔出的PDB不能插到更高版本CDB不是是低版本可插到高版本反向不行。另外字符集、时区设置不一致也可能导致插不进去。实操时我建议先把xml文件和数据文件完整备份到共享存储再开始拔插流程。4.4 快照克隆和刷新克隆的适用场景19c及之后版本支持快照克隆和刷新克隆。快照克隆依赖底层存储快照能力生成PDB瞬间完成适合开发测试环境从生产PDB快速派生环境刷新克隆则可以让克隆出来的PDB周期性地从源PDB刷新数据适合测试库定期同步生产数据。这些玩法听起来美好但对底层存储、版本、以及备份策略都有额外要求。不要在没有测试过的环境里直接上生产我见过有人把快照克隆挂在普通文件系统上结果快照无法创建最后数据文件全是稀疏文件差点把存储空间占满。这类高级功能先搭一套实验环境验证成功了再考虑引用。5. RMAN备份恢复CDB级别与PDB级别的差异5.1 CDB备份与PDB备份的区别多租户环境下RMAN既可以备份整个CDB也可以单独备份某个PDB。两者的区别要理解清楚CDB级备份包含CDB$ROOT、种子PDB、所有PDB以及控制文件和SPFILE。PDB级备份只包含某一个PDB的数据文件以及该PDB的UNDO表空间如果启用了本地UNDO归档日志还是要的。常用的备份命令-- 备份整个CDB backup database plus archivelog; -- 备份单个PDB backup pluggable database salespdb;日常策略推荐控制文件和SPFILE每次自动备份全库备份频率根据恢复目标定针对重要PDB可以额外做单独备份这样在恢复时不必动用整个CDB的全量备份。5.2 单独恢复某个PDB的完整流程当某个PDB崩溃或者数据文件损坏可以单独恢复-- 进入CDB必须以sysdba身份 alter pluggable database salespdb close; -- RMAN中执行还原和恢复 restore pluggable database salespdb; recover pluggable database salespdb; alter pluggable database salespdb open;这一步最怕的是恢复到一半发现归档日志断了。所以在恢复前先确认该PDB的归档日志连续性。如果归档日志有缺失RMAN会自动报错这时候可能需要恢复到更早的备份点或者接受数据丢失。5.3 不完全恢复在PDB中为什么麻烦这里有一个容易想当然的地方对单个PDB做不完全恢复能不能恢复到过去某个时间点严格来说如果你只是单独恢复一个PDB的数据文件到过去时点但CDB里的redo log是所有PDB共享的后续日志里还可能包含其他PDB的事务所以单独做时间点恢复很可能因日志序列不匹配而失败或者与CDB整体数据不一致。实践中对某个PDB做不完全恢复通常做法是先将整个CDB恢复到某个时间点或SCN。再用alter database open resetlogs打开整个CDB。此时所有PDB都会回到这个时间点。也就是说不可能只说我只想把salespdb恢复到昨天下午3点其他PDB完全不动——因为CDB是共日志的。要规避这种窘境最好给关键PDB设计独立的恢复策略比如高频单独备份这样全量恢复最快的粒度和数据丢失量都可控。5.4 备份策略建议从我实际运维的经验来看多租户的备份策略至少要分两层CDB层控制文件、SPFILE每天自动备份保留最近若干天。PDB层按业务重要性区分备份频率。核心PDB每天单独备份普通PDB每周一次全备加每日归档即可。归档日志统一备份因为PDB恢复依赖归档。另外RMAN在CDB模式下如果执行delete obsolete要小心它可能根据备份策略标记PDB备份失效。建议先report obsolete看要删除哪些再执行确认。6. 多租户下的性能与资源控制别让邻居互相打扰6.1 CPU、IO与内存的隔离手段共享实例的好处是省资源坏处是某个PDB可能把CPU打满影响同CDB里的其他业务。Oracle提供了CDB资源管理计划CDB Resource Manager Plan来控制各PDB的资源使用。创建资源管理计划的思路begin dbms_resource_manager.create_cdb_plan( plan cdb_plan, comment Control PDB resource usage ); dbms_resource_manager.create_cdb_plan_directive( plan cdb_plan, pluggable_database salespdb, shares 2, utilization_limit 80 ); dbms_resource_manager.create_cdb_plan_directive( plan cdb_plan, pluggable_database otherpdb, shares 4, utilization_limit 50 ); end; / alter system set resource_manager_plan cdb_plan scope both;这里的shares是权重分配utilization_limit是CPU使用率上限。这样能限制某个PDB最多用多少CPU不至于把整个CDB拖垮。内存方面PDB不能独立设置SGA但可以通过alter system set pga_aggregate_limit在PDB级别限制PGA。如果遇到单个PDB消耗大量内存导致邻居PDB响应变慢可以给有问题的PDB设一个PGA上限缓冲一下影响。6.2 PDB级参数覆盖多租户允许在PDB级别覆盖某些实例参数让不同PDB用不同的优化器模式、并行度等alter system set optimizer_mode first_rows container current scope both;需要注意并非所有参数都支持PDB覆盖。一个简单的判断办法登录到PDB后执行alter system set 参数名 scopeboth如果报ORA-65040说明该参数不允许在PDB级设置。遇到这类情况只能在CDB级统一调整或者考虑把该PDB迁到另一个CDB中。6.3 监控PDB资源消耗日常监控以下几个视图值得常看v$pdbs各PDB的打开模式、状态。v$con_container_stats提供PDB级别的CPU、IO等统计信息。v$resource_limit观察资源限额。v$session里的con_id列定位某个会话属于哪个PDB。有一次客户反馈整个实例慢查AWR发现是某个PDB跑了个超大规模并行查询把CPU全占了。如果没有按PDB维度看统计很难快速定位。所以监控面板里一定要有PDB维度的CPU、会话数、IO指标。6.4 常见的性能干扰与优化经验大查询并行度太高在PDB级限制parallel_max_servers或用资源管理计划限制并行。定时任务冲突多个PDB都在整点跑批CPU峰值叠加。错开调度比硬扛更好。归档风暴某个PDB数据量暴增导致整个CDB归档日志暴涨。通过监控v$con_container_stats找到元凶。资源管理计划的配置不需要过于复杂先把最重要的几个PDB加进去后面再根据监控数据微调。7. 实战中的坑多租户架构最容易翻车的地方7.1 连接串里把PDB名字当service_name用这个问题前面提过但值得再展开。Oracle数据库里每个PDB会注册一个与该PDB名称相同的默认服务名所以很多应用直接拿PDB名当服务名用没什么问题。但如果你自定义了服务名或者通过svcrctl创建了别的服务默认服务名也可能存在此时应用如果连了非期望的服务名可能连到了CDB或另一个PDB。检查应用实际连接的服务名select sys_context(USERENV, DB_NAME), sys_context(USERENV, CON_NAME) from dual;如果返回的CON_NAME不是预期PDB赶紧改连接串。这类问题隐蔽在应用层排查起来最费时间。7.2 PDB默认创建后处于MOUNTED状态新PDB创建后默认MOUNTED很多人以为是创建流程有问题各种查log。其实只要执行alter pluggable database xx open; alter pluggable database xx save state;就解决了。建议把这两条命令固化到新建PDB的操作清单里否则下一次还是要踩。7.3 克隆时忘记将源PDB设为只读执行克隆命令频繁报ORA-65081无法从非只读状态克隆。解决办法alter pluggable database salespdb open read only; -- 克隆完成后 alter pluggable database salespdb open read write;注意如果在克隆期间源PDB要承接业务那就不能直接只读。这时可以考虑用刷新克隆或者通过备库做克隆先把源库相关归档拷贝到备库再用备库的一致状态做源。7.4 字符集与时区不一致造成拔插失败拔插或远程克隆时源PDB和目标CDB的字符集、时区不匹配可能在创建阶段报错。所以迁移规划阶段一定要先做兼容性检查select value from nls_database_parameters where parameter NLS_CHARACTERSET; select dbtimezone from dual;如果字符集不一致最稳妥的办法是先把源PDB的数据导出再导入到目标环境而不是直接拔插。硬拔插有时表面成功了但业务数据出现乱码那是更难解决的事。7.5 大事务导致UNDO暴涨在多租户下的连锁反应共享UNDO模式下未启用本地UNDO某个PDB跑了一个没有提交的大事务UNDO段会被撑大可能直接影响CDB里所有PDB的查询一致性和性能。即便启用了本地UNDO如果一个CDB里有多个PDB同时对UNDO表空间发起扩展也可能把磁盘空间吃满。应对办法包括监控v$undostat关注各PDB的UNDO增长速度限制长事务源头的业务逻辑必要时给利库PDB单独设置更大的UNDO表空间。这些动作不能靠事后补救提前监控更重要。这些经验背后的个人体会多租户架构本身不难理解但实际操作里最考验人的是对共享与隔离边界的把握。CDB与PDB的关系、UNDO是否独立、日志共享导致的不完全恢复限制、连接串里那一点点服务名差异——每一条看起来很小一旦在真实环境里出问题影响面往往比传统架构更大。我个人的建议是任何操作之前先确认当前会话在哪个容器任何PDB级操作先进PDB任何CDB级操作先想清楚会不会误伤邻居。最后再分享一个小技巧写管理脚本时每个关键动作都先输出show con_name再在脚本里用alter session set container显式指定目标PDB。别小看这一行它已经帮我挡住了好几次脚本跑错库的事故。