在cdb$root中创建用户必须使用c##前缀,因sql解析阶段硬编码正则^c##|^c##校验触发ora-65096;合法路径仅两条:在pdb中建本地用户或在cdb$root中用c##前缀加container=all建公用用户。
在 oracle 19c 的 cdb 模式下,create user 在 cdb$root 中执行时,**根本不是“必须带 c## 前缀”,而是“不带就直接被 sql 解析器拦住”**——它压根不会进入后续校验环节,连密码、权限、表空间这些都还没看,就报 ora-65096。
为什么在 CDB$ROOT 执行 CREATE USER 一定触发 ORA-65096
这不是配置没调好,也不是权限不够,是 Oracle 底层函数 ksuCheckCommonUserName 在 SQL 解析阶段做的硬编码正则匹配:^C##|^c##(大小写敏感,两个井号必须连续开头)。任何偏离这个格式的用户名,比如 app_user、c#user、cc##test,甚至加双引号 "myuser",全都会被当场拒绝。
这个校验完全不读 common_user_prefix 参数,也不受 _oracle_script=TRUE 影响——后者只是让某些内部脚本绕过检查,但会破坏数据字典一致性,官方明确不支持用于建用户。
-
SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL;返回CDB$ROOT?那当前就在触发点上 -
SELECT CDB FROM v$database;返回YES?确认是多租户架构 - 错误信息里 “common user” 是术语,不是“普通用户”,指跨 PDB 生效的全局用户
真正有效的两种路径:切容器 or 接受前缀
所谓“跳过”,实际只有两条合法路径,没有第三条:
- 在目标
PDB中创建本地用户:先ALTER SESSION SET CONTAINER = pdb_name;,再CREATE USER app_user IDENTIFIED BY pwd;—— 这时命名完全自由,和 11g 一样 - 在
CDB$ROOT中创建公用用户:必须用C##前缀 + 显式CONTAINER = ALL,例如CREATE USER c##admin IDENTIFIED BY pwd CONTAINER = ALL;
漏写 CONTAINER = ALL 就默认 CONTAINER = CURRENT,结果是在 CDB$ROOT 里建了个“本地用户”,但 CDB$ROOT 不允许本地用户,直接报 ORA-65094。
tnsnames.ora 和连接串容易踩的坑
你以为 conn / as sysdba 登录后 ALTER SESSION SET CONTAINER = hr_pdb; 就完事了?不一定。很多工具(如 Navicat、SQL Developer)默认连接的是 CDB service_name(比如 ORCL),即使你切了 container,下次新建查询窗口又回到 CDB$ROOT。
- 必须在
tnsnames.ora中单独配一个指向 PDB 的条目,例如:HRPDB = (DESCRIPTION = ... SERVICE_NAME = hr_pdb) - 连接时用
conn system@hrpdb,而不是conn system@orcl -
SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL;要每回都查,别凭记忆判断当前容器
强行改参数或 hack data dictionary 是自找麻烦
网上流传的 ALTER SYSTEM SET common_user_prefix = '' 或 UPDATE user$ 直接改用户名,短期看似生效,但后果严重:
-
common_user_prefix在 12.1+ 已废弃,重启后可能失效,且不影响ksuCheckCommonUserName的硬编码逻辑 - 手动更新
user$表会破坏 Oracle 内部一致性校验,升级、打补丁、Data Guard 同步时大概率失败 -
_oracle_script=TRUE是 undocumented 隐藏参数,开启后不仅影响用户创建,还可能让其他元数据操作绕过安全检查
最常被忽略的一点:绝大多数业务应用根本不需要跨 PDB 的公用用户。你花一小时折腾绕过 C##,不如花五分钟配好 PDB 连接,然后像老版本一样建用户——这才是 Oracle 官方推荐的路径。











