news 2026/10/20 6:01:37

Oracle多租户架构核心解析:CDB与PDB的容器化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Oracle多租户架构核心解析:CDB与PDB的容器化实践

1. 多租户架构的设计思路:数据库整合与资源利用

Oracle多租户容器数据库,是Oracle在12c版本推出的核心架构变革,官方名称是Multitenant Architecture。很多人一开始听到“容器”这个词容易发懵——实际上可以把它理解为一种“合租”模式:一个CDB(Container Database,容器数据库)实例,像一套公寓的管理系统,里面可以住很多个PDB(Pluggable Database,可插拔数据库),每个PDB就是一间独立房间。

在12c之前,Oracle的架构是“一个实例对应一个数据库”,哪怕你只用一个很小的库,也需要单独分配一大块内存,启动一整套后台进程。几套系统下来,服务器内存被吃得干干净净,运维复杂度也跟着涨。做过几年DBA的人应该深有体会:十几个库分布在几台服务器上,每套库都要单独备份、单独打补丁、单独监控,光是维护这些重复劳动就非常耗时。

多租户架构的引入,本质上就是为了解决这三个痛点:

  • 降低资源消耗:一个CDB实例共享一套内存结构(SGA/PGA)和后台进程,多个PDB共用这套资源,而不是每个库各起一套实例。
  • 提升交付效率:PDB的克隆(Clone)、拔出(Unplug)、插入(Plug)机制,让“快速搭建一套新环境”从几小时缩短到几十分钟,甚至是几分钟。
  • 简化升级维护:补丁、升级可以在CDB级别统一操作,PDB共享底层安装文件,不用一套一套去打补丁。

当然,“合租”也有它的代价。多租户架构引入了新的管理维度,比如公共用户与本地用户、容器间切换、PDB状态管理,这些在传统单机架构里是没有的概念。很多人第一次接触时经常搞混CDB与PDB的关系,比如在CDB里创建了一个普通用户却报错“ORA-65096”,这就是没搞懂公共用户命名规则导致的。

这套架构适用于各种规模的使用场景。企业做多套系统整合,开发环境快速搭建测试库,SaaS平台做多租户隔离,都可以受益。如果你是数据库管理员、运维工程师、架构师,或者正在学习Oracle认证,多租户都是绕不开的重点。接下来把架构中的关键组件和实操逻辑逐一拆开讲清楚。

2. 核心组件拆解:CDB、PDB、种子容器与用户体系

2.1 CDB、PDB与容器层级关系

要理解多租户,首先要搞清楚这三个容器的角色:

  • CDB$ROOT(根容器):整个CDB的顶层容器,存放Oracle系统元数据、系统表空间、数据字典,以及所有PDB共有的对象定义。它像一个“总管理室”,不存用户业务数据。
  • PDB$SEED(种子容器):创建新PDB时的模板,它是一个只读的PDB,所有新PDB都由它复制而来。种子的Expense不能修改,是Oracle系统的保留模板。
  • PDB(可插拔数据库):承载用户业务数据和应用程序元数据的容器,多个PDB之间在逻辑上相互隔离。

从数据字典的角度来看,CDB中引入了CON_ID(容器ID)和CON_NAME(容器名称)这两个概念。比如查V$PDBS视图可以看到当前CDB里有哪些PDB,查V$CONTAINERS可以看到所有容器的信息。每次连接到一个PDB工作时,实际上就在某个容器上下文(Container Context)中执行SQL。

举个例子,假设CDB名为CDB1,里面创建了PDB1、PDB2两个PDB。这时如果你用ALTER SESSION SET CONTAINER = PDB1;切换容器,再执行SHOW CON_NAME,返回的是PDB1。之后查询的表默认都在PDB1的范围内,而不会跨到PDB2里去。这种机制保证了PDB之间的逻辑隔离。

2.2 公共用户与本地用户的区别

很多人第一次使用多租户时会踩一个坑:想创建一个普通业务账号,直接在CDB$ROOT里执行CREATE USER app_user IDENTIFIED BY password;,结果Oracle直接报错:

ORA-65096: invalid common user or role name

这个报错的意思是:在CDB中创建用户时,用户名必须用C##(或c##)开头,比如C##ADMIN,因为这类用户是公共用户(Common User),可以在所有容器中登录。而普通用户(Local User)只能创建在某个PDB中,名字不能以C##开头。

这背后的设计意图很明确:公共用户和本地用户的作用域完全不同。

  • 公共用户(Common User):在CDB$ROOT中创建,密码和角色在整个CDB中统一管理。DBA账号、监控账号、备份账号这类需要跨容器操作的账号,适合做成公共用户。C##DBA登录后可以在任何PDB中执行操作(只要该用户有相应权限)。
  • 本地用户(Local User):在某个PDB中创建,只在该PDB内有效。业务应用连接数据库使用的账号,绝大多数都是本地用户。各个PDB之间可以存在重名的本地用户,互不影响。

从运维习惯上讲,我建议按这个规则来:所有管理类操作优先用公共用户,所有业务类操作必须用PDB本地用户。避免用C##账号直连业务库跑日常业务SQL,这样能减少误操作把数据改到别的PDB里的风险。

2.3 服务名与PDB的连接方式

多租户架构下的数据库连接和服务名规则也发生了变化。在旧架构中,连接字符串通常写orcl这个全局数据库名(SERVICE_NAME)。到了CDB时代,每个PDB都用自己的服务名对外提供服务。

假设CDB1的全局名称是CDB1,PDB1的服务名默认是pdb1。客户端连接时使用:

jdbc:oracle:thin:@//192.168.1.10:1521/pdb1

这时的连接落点就是PDB1,而不是CDB1。想要连到CDB本身,应该使用CDB的服务名(即CDB的全称),例如:

jdbc:oracle:thin:@//192.168.1.10:1521/cdb1

有一点需要特别提醒:在实际项目中,连接PDB时建议始终使用PDB自身的服务名,而不是CDB的服务名。原因在于,如果应用连接串指向了CDB,很可能会在ROOT容器上下文中执行SQL,产生意料之外的权限问题或者数据字典查询开销,尤其是某些应用会执行SELECT * FROM USER_TABLES,在CDB中查到的结果范围与在PDB中完全不同,极易引起误判。

3. 实操过程:创建CDB、PDB与容器切换

3.1 通过DBCA创建CDB

创建CDB有两条路径:Oracle提供的DBCA图形化工具,或者手工执行CREATE DATABASE语句。日常运维中,绝大多数环境都选择DBCA,它对参数文件的处理、数据字典的生成、监听服务的注册都自动完成,省去很多手动步骤。

DBCA创建CDB时有几个关键选项需要理解:

  • 创建为容器数据库(Create as Container Database):这个勾选框必须选中,否则创建的实例还是一个传统意义上的非CDB数据库。
  • PDB名称:你可以在创建时就指定一个或多个PDB名称。这里填写的PDB名称就是后续客户端连接时的服务名主体。
  • 启用本地UNDO(Enable Local Undo):这是必须开启的选项,多租户架构中每个PDB必须拥有独立的UNDO表空间,且PDB的UNDO信息无法跨容器共享。从12c R2开始,本地UNDO已经是唯一支持的UNDO模式。
  • 样本Schema(Sample Schemas):可选加载演示数据。如果仅仅做测试,加载样本数据可以帮助快速验证PDB内的基本操作,不过实际生产环境一般不需要勾选。

在静默模式下,DBCA的响应文件(响应文件模板)可以提前定好CDB名称、监听端口、PDB列表、SYS密码等参数,适合批量部署环境。举个例子,某个测试平台需要一次性搭建多个CDB,每个CDB包含两个PDB,用响应文件自动部署几乎不需要人工介入。

我建议初学者走一遍DBCA图形界面流程,重点观察它生成的参数文件和监听注册信息,这对理解CDB与PDB的关系很有帮助。

3.2 手工创建PDB的几种方式

在实际工作中,我创建PDB用得比较多的方法是三种:从种子创建、通过克隆创建、通过拔插迁移。

从种子创建PDB是最基础的方式。执行创建语句时,Oracle会把PDB$SEED中的数据文件复制一份,形成新PDB的数据文件。命令示例如下:

CREATE PLUGGABLE DATABASE appdev ADMIN USER appdev_admin IDENTIFIED BY appdev_pwd FILE_NAME_CONVERT = ('/u01/app/oracle/oradata/CDB1/pdbseed', '/u01/app/oracle/oradata/CDB1/appdev');

如果不想手动指定FILE_NAME_CONVERT,也可以配置DB_CREATE_FILE_DEST参数配合OMF(Oracle管理文件),这样文件路径由Oracle自动管理,简单很多。

这里需要特别关注一个容易被忽略的点:ADMIN USER子句创建的用户是在PDB内部的本地用户,它拥有PDB内CREATE SESSION、CREATE TABLE等基础权限,但默认并不会拥有DBA角色。如果需要给这个PDB安一个带DBA权限的管理员,必须在创建后进一步授权:

ALTER SESSION SET CONTAINER = appdev; GRANT DBA TO appdev_admin CONTAINER = CURRENT;

联机克隆是让人干活最省时间的方式。假设要基于生产PDB快速搭建一个测试PDB,可以执行:

CREATE PLUGGABLE DATABASE appdev_test FROM appdev FILE_NAME_CONVERT = ('/u01/app/oracle/oradata/CDB1/appdev', '/u01/app/oracle/oradata/CDB1/appdev_test');

克隆过程会自动把源PDB的数据文件复制出来,在克隆完成后源PDB和克隆出来的PDB在数据上是完全一致的(直到新的PDB开始发生写入)。如果源PDB的表空间使用了加密、或者存在活动中的长事务,克隆可能失败,这一点在实操中要提前规避。

拔出/插入适合PDB在不同CDB之间迁移的场景。先把PDB从源CDB中拔出:

ALTER PLUGGABLE DATABASE appdev CLOSE IMMEDIATE; UNPLUG INTO '/home/oracle/appdev.xml'; DROP PLUGGABLE DATABASE appdev KEEP DATAFILES;

它会生成一个XML描述文件,描述PDB的结构和数据文件位置。之后把这个PDB插入到目标CDB:

CREATE PLUGGABLE DATABASE appdev USING '/home/oracle/appdev.xml' COPY FILE_NAME_CONVERT = ('/u01/oracle/data/appdev', '/u01/oracle/data/appdev_bak');

注意APEX组件、补丁版本、字符集等细节在跨CDB插入时必须匹配,否则会报错。通常我会提前用DBMS_PDB.CHECK_PLUGGABILITY检查兼容性。

3.3 PDB的启动与关闭

PDB的启停是独立于CDB的。实例STARTUP时,所有PDB默认都是MOUNT或OPEN,具体取决于PDB的保存状态(Saved State)。也可以手动打开某个PDB:

ALTER PLUGGABLE DATABASE appdev OPEN;

要关闭某个PDB,先确保里面没有业务会话,然后执行:

ALTER PLUGGABLE DATABASE appdev CLOSE IMMEDIATE;

实际使用中我发现一个常见问题:很多应用的连接池在PDB关闭后还会缓存连接,导致应用端报“ORA-01033: ORACLE initialization or shutdown in progress”或者连接超时。因此在下线维护PDB之前,最好先通知应用团队停掉相关服务,或者直接修改服务配置让连接池指向其他PDB,避免大量报错堆在应用端。

另外,容器状态可以设置为“保存状态”。使用如下命令将某个PDB置为OPEN状态并保存:

ALTER PLUGGABLE DATABASE appdev OPEN; ALTER PLUGGABLE DATABASE appdev SAVE STATE;

这样后续CDB实例重启时,PDB会自动打开。若执行ALTER PLUGGABLE DATABASE appdev DISCARD STATE;,则恢复为默认行为(实例重启后PDB保持MOUNT状态)。在做容灾演练、多节点集群运维时,这个功能非常有用——不用每次重启后逐一手动打开PDB。

3.4 容器切换与常用视图

在运维过程中,经常需要在CDB与PDB之间来回切换。切换容器的命令非常直接:

ALTER SESSION SET CONTAINER = appdev; SHOW CON_NAME;

登录到CDB后,如果直接执行SELECT * FROM TAB;,查到的只是ROOT容器里的对象,业务表通常查不到。查询某个PDB里的表,必须先把会话切换到对应PDB。这是新手最常犯的错误之一。

有几个视图在日常维护中几乎天天用到:

  • V$CONTAINERS:列出CDB中的所有容器信息。
  • V$PDBS:全部PDB及其当前状态。
  • DBA_PDB_SAVED_STATES:查看哪些PDB设置了保存状态。
  • V$ACTIVE_SESSION_HISTORY(ASH):多租户下需要有权限才能跨容器查看。

如果想快速批量查看所有PDB的状态,可以执行:

COLUMN name FORMAT A20 SELECT name, open_mode FROM v$pdbs;

输出结果会清楚地把每个PDB的当前打开方式列出来,一眼就能看出哪个PDB没开。

4. 常见问题与排错实录

4.1 ORA-65096:公共用户命名规则

前面已经提到,这个报错是CDB中创建用户时没有使用C##前缀导致的。除了创建用户之外,在CDB中创建角色也受同样约束,比如CREATE ROLE C##MONITOR_ROLE;才合法,CREATE ROLE monitor_role;会直接报错。

处理方式很简单:

CREATE USER C##admin IDENTIFIED BY admin_pwd;

不过要注意,如果你确实只想创建一个PDB内的业务用户,切换容器后再创建即可:

ALTER SESSION SET CONTAINER = appdev; CREATE USER app_user IDENTIFIED BY app_pwd;

4.2 ORA-65040:操作不允许在PDB内部执行

PDB内部不允许执行某些CDB级操作,比如创建公共用户、修改数据库系统参数(某些ALTER SYSTEM命令受限)、创建UNDO表空间等。遇到报错时,先检查当前容器。

SHOW CON_NAME;

如果当前容器是某个PDB,而你确实需要执行的是CDB级别操作,把会话切回CDB$ROOT:

ALTER SESSION SET CONTAINER = CDB$ROOT;

一种很常见的场景是:在PDB里执行ALTER SYSTEM SET optimizer_mode = ...之类的参数修改被拒绝。这不一定是因为命令本身不行,而是参数的作用范围分成了“仅CDB级”和“PDB可覆盖”两种。可以在V$SYSTEM_PARAMETER中查参数是否允许在PDB级别覆盖(ISPDB_MODIFIABLE列)。

4.3 克隆PDB时因TDE或加密表空间报错

如果源PDB的表空间开启TDE加密,而目标CDB的加密主密钥或密钥匙存储没有配置,克隆会直接失败,数据库会提示类似“ORA-28365: wallet is not open”的信息。

解决办法:确保该CDB上的Wallet已经打开,并且使用了相同的主密钥。测试环境一般不需要TDE,直接忽略;但生产环境中强烈建议先确认钱包配置,再针对加密表空间的特殊目录设置进行克隆。

4.4 PDB无法打开一直处于MOUNT状态

日常维护中经常遇到CDB实例重启后PDB还是MOUNT状态。有次某测试环境里,PDB怎么都打不开,报“ORA-01109: database not open”。排查思路如下:

  • 先查V$PDBS确认当前状态。
  • 如果处于MOUNT,尝试ALTER PLUGGABLE DATABASE appdev OPEN;。
  • 如果打开报错,检查目标PDB的告警日志,通常能看到具体原因(比如UNDO表空间文件丢失、数据文件路径不存在)。
  • 如果之前做过拔插迁移,要检查XML文件和数据文件路径是否一致。

一个我已经踩过多次的教训:PDB里的数据文件路径如果在文件系统里被移动过,但控制文件里的路径没有同步更新,那PDB就会卡在MOUNT状态。这时候通过ALTER DATABASE RENAME FILE重命名数据文件路径可以解决。多租户环境下每个PDB的数据文件路径都比较集中,一旦路径对不上,用DBA_DATA_FILES视图和服务器文件系统里的实际路径对照检查是最快的定位方式。

4.5 连接串连不上PDB

连接不上PDB是误配置服务名导致的居多。步骤如下:

  • 先检查监听是否注册了PDB服务:lsnrctl status,看看列出哪些服务名。
  • 再到数据库里查V$SERVICES视图,确认PDB是否正常提供数据库服务。
  • 确认客户端连接串里的SERVICE_NAME大小写敏感,别写错了。

有次环境里PDB服务名appdb,但应用连的是APPDB,由于有些平台上服务名的匹配规则比较严格,就是连不上。这种问题排查起来不复杂,但很耽误事。建议把计划使用的连接串先在sqlplus里做一次连通性测试,再交给应用团队,能省不少沟通成本。

4.6 UNDO表空间告警或无空间

每个PDB拥有独立的本地UNDO表空间,万一某个PDB的UNDO表空间空间不足,只影响该PDB内的事务,而不至于拖垮整库。如果收到“undo tablespace is near full”的告警,按以下顺序排查:

  • 在对应PDB中查询DBA_UNDO_EXTENTS,查看是否有未提交的长事务。
  • 查询最长运行的SQL、长时间未提交的事务会话。
  • 如果的确有长事务,对应用进行干预,提交或回滚事务。
  • 确认空间确实紧张时,再添加UNDO数据文件。

多租户下UNDO独立实际上是个很实用的设计。以前传统架构一旦某个业务事务把UNDO耗尽,整个实例都要遭殃。现在PDB之间可以做更细粒度的隔离和限制,这算是我对多租户架构最满意的点之一。

5. 运维经验与避坑建议

5.1 备份策略也要“按PDB走”

多租户架构的备份恢复粒度发生了变化。传统的RMAN备份通常针对整个CDB,但在多租户下你可以只针对某个PDB做备份恢复,不影响其他PDB的正常运行。RMAN命令示例:

RMAN> BACKUP PLUGGABLE DATABASE appdev; RMAN> RECOVER PLUGGABLE DATABASE appdev;

这种细粒度备份对多业务的整合环境特别有利。A业务的PDB出问题了,不用把B、C业务的PDB也一起恢复,大大降低了恢复时间。

不过需要注意,备份CDB级的系统表空间、根容器和种子容器同样重要。如果CDB的ROOT容器出现问题,所有PDB都无法工作,所以备份策略上要两者兼顾:CDB全备或者CF备份+归档日志,搭配各PDB的单独备份。

5.2 补丁升级的注意事项

12c到19c,特别是更早的多租户版本,补丁升级通常推荐“补丁在CDB级别统一安装”,这样所有PDB都共享同一份Oracle主目录中的二进制文件,不需要每个PDB单独升级。但数据库内部的数据字典版本组件升级,却需要每个PDB分别执行。

很多人在升级补丁后没有执行PDB级别的catcon.pl脚本,导致PDB仍然显示旧版本或者状态不一致。后来从23c开始补丁流程有所简化和演进,但在19c、21c环境里,这种CDB与PDB两段式升级的坑依然大量存在。升完补丁后一定要检查所有PDB的DBA_REGISTRY_SQLPAT和DBA_REGISTRY_HISTORY,确保状态一致。

5.3 合理规划PDB数量

PDB并非越多越好。虽然一个CDB可以承载几百个PDB,但并不建议盲目堆砌。PDB之间共享SGA和后台进程,如果某个PDB里的业务并发很高,消耗大量内存和CPU,其他PDB的使用体验就会受到波及,多租户的隔离是逻辑隔离,不是硬件隔离。

在规划初始方案时,建议把不同量级的业务拆分到多个CDB里。比如OLTP业务放CDB_A,OLAP报表类放CDB_B,再严格限制每个PDB的资源用量(Resource Manager支持在CDB内对PDB做I/O和CPU限制)。这样即便某个PDB爆发性负载,受到影响的也只是同CDB内的邻居。

5.4 从19c到23c的变化

随着Oracle更新,多租户架构本身也在逐步演进。19c是绝大多数企业仍然在使用的版本,稳定性、成熟度都相当高。23c引入了更多多租户相关的功能点,比如PDB级别的闪回、更灵活的资源管控等,但底层的CDB/PDB框架没有根本性变化。理解19c这套逻辑之后,切换到更新的版本只需要关注增量功能就够了。

当前环境下,把多租户容器数据库作为学习和应用的方向仍然很有价值。对于个人开发者来说,在一台服务器上通过一个CDB开好几个PDB,模拟多套系统环境,成本和复杂度都比以前低很多。对于企业DBA来说,多租户的多PDB整合、快速克隆、细粒度备份恢复了实实在在的生产力。

6. 扩展思路:多租户的后续玩法

多租户这块学好之后,后面的路其实还很多。举个例子,结合数据泵(Data Pump)和PDB克隆,可以在几分钟内“变”出一个接近生产库的数据集,供开发测试使用。联合外部表、物化视图这些常规能力,把多套系统的数据整合在一起也方便很多。

测试环境里,我习惯保留一个干净的PDB作为“模板库”,里面预置常用用户、常用表结构和基础数据。需要搭新项目时,直接克隆一份,连建库脚本都省了。这样既统一了环境标准,又减少重复劳动。个人在实操中体会最深的一句话是:多租户玩得好不好,不在于你敲了多少命令,而在于你对“共享”和“隔离”这两个词的理解有多深。搞清楚它们在不同场景下的边界,很多原本复杂的问题都会变得很简单。

当然,如果项目规模不大,只有一个库,是否一定要用多租户?这倒不一定。但即便使用传统架构,多租户的概念和技能依然值得掌握——现在的很多新功能、新工具、新版本特性都嵌入了容器化思维。作为DBA或者数据库爱好者,早一点搞清楚这套东西,后面应对各种新架构都会更从容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:00:53

Go 开发踩坑实录:Slice 最常犯的10个错误

作为常年写Go的后端开发,slice是日常编码最高频的数据结构。很多人以为slice就是动态数组,但底层是数组指针长度容量的复合结构,很多bug都源于对底层原理理解不深。下面整理了实际项目里最容易踩的10个坑 1. 误以为slice是值类型,…

作者头像 李华
网站建设 2026/10/20 6:00:32

【打造能干活的Agent】判断资料任务该用工作流还是 Agent

判断资料任务该用工作流还是 Agent:用一套验收标准做架构选择课程归属:打造能干活的 Agent|第 2 节处理资料时,很多需求看起来都像“让 AI 帮我分析”:有的只需按固定步骤读取文件、提取字段并生成报告;有的…

作者头像 李华
网站建设 2026/10/20 6:00:11

AI产品经理能力跃迁:从会用AI到能落AI的实战拆解

最近后台收到好多类似的留言:我也天天用AI,写提示词也算熟练,为什么一到项目里就做不出来?“AI产品经理岗位到底在要求什么样的人?”恰好我关注到一个信息,某头部知识问答社区把内部沉淀的AI训练营推上了公…

作者头像 李华
网站建设 2026/10/20 6:00:10

符号接地:具身智能泛化失败的核心症结

近两年“具身智能”这个词频繁出现在各种技术讨论里,大家一边惊叹于机器人能在模拟环境里翻跟头、抓取物体,一边又发现这些能力一旦放到真实世界就变得极其脆弱。我自己的感受特别深:模型在训练集里明明已经“认识”了杯子,换个灯…

作者头像 李华
网站建设 2026/10/20 6:00:01

从零搭建企业级实时数据中台:架构选型与实战避坑指南

做企业级实时数据中台这件事,我前后折腾了快一年。从最初只有几张离线T1报表,到后来能支撑业务方实时看数、实时风控、实时大屏,中间踩过的坑、推倒重来的架构,足够写成一本书。今天这篇就把我从零搭建实时数据中台的完整路径拆给…

作者头像 李华