必须在cdb$root中执行,用户名以c##或c##开头,且显式指定container=all——三者缺一不可;否则用户仅存在于cdb$root,pdb中不可见,授予权限也须加container=all。

必须在CDB$ROOT中执行,用户名以c##或C##开头,且显式写CONTAINER=ALL——三者缺一不可,否则用户只存在于CDB$ROOT,PDB里根本看不到。
创建前先确认当前容器是CDB$ROOT
连上后别急着敲CREATE USER,先查会话所在容器:
SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL;
返回值必须是CDB$ROOT。如果返回的是某个PDB名(比如ORCLPDB1),说明你已经切到PDB了,得先退出重连,或执行:
ALTER SESSION SET CONTAINER = CDB$ROOT;
常见陷阱:
- 用
sqlplus / as sysdba登录,默认进的就是CDB$ROOT,但某些GUI工具(如SQL Developer、Navicat)默认连接的是CDB service,新建窗口仍可能回到CDB$ROOT,容易误判 -
SHOW CON_NAME和SELECT ... FROM V$PDBS不是一回事:V$PDBS查的是所有PDB状态,CON_NAME查的是当前会话容器
CREATE USER语句必须同时满足三个硬性条件
Oracle内核在解析阶段就用正则^C##(大小写敏感)校验用户名开头,不走配置、不看引号、不读参数:
- 用户名必须严格以
C##或c##开头,c#admin、"C##user"、C##user(带双引号)都不行 - 不能指定PDB专属的表空间作为
DEFAULT TABLESPACE,否则报ORA-65048;应选USERS这类CDB级通用表空间 -
CONTAINER=ALL必须显式写出,漏掉或写成CONTAINER=CURRENT,用户就只是个“隐身本地用户”,只在CDB$ROOT里存在
正确示例:
CREATE USER c##appadmin IDENTIFIED BY pwd123 DEFAULT TABLESPACE users CONTAINER=ALL;
授予权限时CONTAINER=ALL同样不可省略
创建成功 ≠ 能登录PDB。公共用户默认在任何容器都没有CREATE SESSION权限:
-
GRANT CONNECT TO c##appadmin;→ 权限只生效于当前容器(即CDB$ROOT) -
GRANT CONNECT TO c##appadmin CONTAINER=ALL;→ 才能在所有已OPEN的PDB中登录 - 即使角色本身是公用的(如
C##HR_ROLE),授权时也必须加CONTAINER=ALL,否则权限不跨容器 - 若PDB未处于
READ WRITE或READ ONLY状态(比如是MOUNTED),CONTAINER=ALL会跳过该PDB并报警告,不会报错但也不生效
验证是否真正跨容器可用,别查DBA_USERS(它只反映当前容器),改用:
SELECT username, con_id, created FROM cdb_users WHERE username = 'C##APPADMIN';
看到con_id = 1(CDB$ROOT)和con_id = 3(某PDB)都存在,才算成功。
PDB中已有同名本地用户怎么办?
如果PDB里早有appadmin这个本地用户,又在CDB$ROOT建了c##appadmin,两者完全独立,互不影响:
- 登录时用
conn appadmin/pwd@pdb_service进的是本地用户 - 用
conn c##appadmin/pwd@cdb_service进的是公共用户 - 不存在“覆盖”或“升级”关系,也不会冲突,但容易让人误以为权限打通了——其实它们的
SCHEMA、OBJECTS、QUOTA全都不共享
最易被忽略的一点:CONTAINER=ALL只控制权限分发范围,不控制对象可见性。你在PDB里SELECT * FROM c##appadmin.emp会报ORA-00942,因为公共用户的对象默认不在PDB的默认schema路径里,必须显式加c##appadmin.前缀,或设置ALTER SESSION SET CURRENT_SCHEMA = c##appadmin;。











