☰
Oracle ORA-01950权限报错:定义者权限包中角色失效的排查与修复
2026/10/8 3:11:27 网站建设 项目流程

事情要从一次发布窗口的报错说起。应用侧有个自动化部署流程,其中一步是在一个 19c 数据库里通过存储过程动态建表,结果报出了:

ORA-01950: no privileges on tablespace 'TBS_APP_DATA'

第一反应通常是“用户没配额”,但查下来却让人懵:登录用户app_exec的默认表空间是TBS_APP_DATA,数据库里也建过角色,角色又带着UNLIMITED TABLESPACE,手动在 SQL*Plus 里执行同样的建表语句也能成功。也就是说,表面上权限文件没有任何问题,报错却很诚实:在这个 tablespace 上,当前执行上下文就是没有权限。

这种“权限明明有,却报 no privileges on tablespace”的现场,我后面又遇到过几次,问题都藏在执行环境和权限传递方式上。这篇就把这次排查的完整过程、背后原理和另外几种高频诱因一次说清楚。

1. 症状:权限表看着“井井有条”,偏偏报 ORA-01950

先说这次现场的具体配置,方便对号入座。

数据库版本是 Oracle 19c,用户情况简化之后大概是这样:

  • 用户:app_exec
  • 默认表空间:TBS_APP_DATA
  • 临时表空间:TEMP
  • 直接授予的系统权限:CREATE SESSION、CREATE TABLE
  • 角色:APP_TS_ROLE
  • 角色内包含:UNLIMITED TABLESPACE

从 DBA 视角看,app_exec既建了表,又有无限表空间权限,还套了一层角色,怎么看都不该报“no privileges on tablespace”。

但报错就是在自动化流程里出现了,而且是一闪而过,日志里只有一句:

ORA-01950: no privileges on tablespace 'TBS_APP_DATA'

自动化部署的代码大致是这样:

-- 伪代码:部署平台调用 PL/SQL 包,包内部执行 DDL BEGIN PKG_DEPLOY.RUN_CREATE_TABLE; END;

包PKG_DEPLOY内部再对CREATE TABLE做动态执行,表名和表结构都是拼接出来的。建表语句本身没问题,权限预期也没问题,但执行到EXECUTE IMMEDIATE 'CREATE TABLE ...'那一步,数据库直接拿配额问题打回。

更蹊跷的是,同一个人用同样的app_exec登录 SQL*Plus,手工执行同一条CREATE TABLE语句,能建出来。

-- 手工执行,成功 CREATE TABLE T_TEST (ID NUMBER);

这就形成了一个很典型的“矛盾现场”:手工可以,脚本不行;权限齐全,报错却是权限类错误。

2. 先还原理:ORA-01950 里的 privileges 指的是什么

很多人一看到ORA-01950: no privileges on tablespace就本能地跑去看USER_TS_QUOTAS,这没错,但不全面。这个错误里的 “privileges”,指的是“在这个表空间上建立段的授权”,也就是常说的tablespace quota(表空间配额)。

2.1 建一张表,其实需要两类授权

在 Oracle 里,建表不是一个“有 CREATE TABLE 就能随便建”的动作。严格来说,至少要同时满足两个条件:

  1. 拥有对象 DDL 权限,比如CREATE TABLE,保证你有权在自身 schema 或指定 schema 里建表。
  2. 拥有目标表空间的存储授权,也就是表空间配额,保证你有权在该表空间里分配存储段。

第二类授权有两种存在形式:

  • 用户级显式配额:ALTER USER app_exec QUOTA 100M ON TBS_APP_DATA
  • 系统权限UNLIMITED TABLESPACE,等价于所有表空间都不受限

所以,如果用户只有CREATE TABLE,而在某个表空间上既没有显式配额,也没有UNLIMITED TABLESPACE权限,在这个表空间建表就会触发ORA-01950。

2.2 配额的“显式配额”和“系统权限”是两回事

操作习惯上,很多人把UNLIMITED TABLESPACE当成一种“通用配额”,但它本质上是一个系统权限,不是表空间对象上的一个属性。

这两者有明显区别:

  • 显式配额是用户与表空间之间的绑定关系,比如QUOTA UNLIMITED ON TBS_APP_DATA。
  • UNLIMITED TABLESPACE是给用户或角色授予的一条系统权限,像一把万能钥匙,对所有表空间生效。
  • 显式配额无法授予给角色,语法上就不存在GRANT QUOTA ... TO role这种写法;但UNLIMITED TABLESPACE可以被授予给角色。

熟悉权限体系的人看到这里应该已经察觉到关键点了:既然UNLIMITED TABLESPACE是通过角色授予的,那问题就可能出在“角色有没有被当前执行环境认可”。

2.3 顺手把容易混淆的两个报错分开

排查时最怕把ORA-01950和ORA-01536混在一起。它们经常成对出现,但含义完全不同。

报错含义典型提示
ORA-01950: no privileges on tablespace 'xxx'用户在该表空间上没有任何可用配额没授权、授权丢失、授权在执行环境里不生效
ORA-01536: space quota exceeded for tablespace 'xxx'用户有配额,但配额已经被用完配额耗尽,需要扩容或提升配额上限

ORA-01950更像“敲门权限都没有”,ORA-01536是“门能进但里面空间不够”。这次报的ORA-01950,说明问题不在空间容量,而在于权限识别链路。

2.4 排查入口:别只盯着一张视图看

排查这类问题,数据字典视图的分工要先理清楚:

视图作用注意点
DBA_USERS默认表空间、临时表空间、账户状态决定“没写表空间时建到哪”
USER_TS_QUOTAS/DBA_TS_QUOTAS显式表空间配额无法完全体现UNLIMITED TABLESPACE权限带来的效果
DBA_SYS_PRIVS/USER_SYS_PRIVS直接授予用户的系统权限不含通过角色继承的权限
DBA_ROLE_PRIVS/USER_ROLE_PRIVS用户被授予的角色只说明“有角色”,不说明“角色已启用”
ROLE_SYS_PRIVS角色拥有的系统权限用来反查角色内容
SESSION_ROLES/SESSION_PRIVS当前会话实际生效的角色和权限真正的运行时授权快照

SESSION_ROLES和SESSION_PRIVS是这次排查中最关键的视图,因为它们回答的是一个和“用户被授予了什么”完全不同的问题:“当前这个 session 在这个时刻到底认哪些权限”。

3. 模拟复现:同一个 DDL,直接 SQL 能建,包内执行就报错

光有理论不够,我后来把现场缩小成了一个最小复现环境。下面这段在任何 19c 测试库上都可以重现,不建议直接在生产环境折腾。

3.1 先搭一个最小环境

-- 建两个测试表空间 CREATE TABLESPACE TBS_APP_DATA DATAFILE '/u01/app/oracle/oradata/ORCL/tbs_app_data01.dbf' SIZE 100M AUTOEXTEND ON NEXT 10M; CREATE TABLESPACE TBS_OTHER DATAFILE '/u01/app/oracle/oradata/ORCL/tbs_other01.dbf' SIZE 100M; -- 建测试用户 CREATE USER app_exec IDENTIFIED BY "Oracle123" DEFAULT TABLESPACE TBS_APP_DATA TEMPORARY TABLESPACE TEMP; -- 只给最基础的建表权限 GRANT CREATE SESSION TO app_exec; GRANT CREATE TABLE TO app_exec; -- 建一个角色,把 UNLIMITED TABLESPACE 放到角色里 CREATE ROLE APP_TS_ROLE; GRANT UNLIMITED TABLESPACE TO APP_TS_ROLE; -- 把角色授给用户 GRANT APP_TS_ROLE TO app_exec;

注意,这里故意没有给app_exec任何显式配额,也没有直接授予UNLIMITED TABLESPACE。唯一的“无限表空间能力”在角色APP_TS_ROLE里。

3.2 直接 SQL 会话:成功

用app_exec登录 SQL*Plus:

CONNECT app_exec/"Oracle123"@ORCL CREATE TABLE T_TEST (ID NUMBER);

结果是成功的。因为在普通 SQL 会话里,默认所有角色都会启用,APP_TS_ROLE带着的UNLIMITED TABLESPACE在会话内有效,所以表空间配额检查可以直接通过。

3.3 匿名块:也能成功

在 SQL*Plus 里直接用匿名块,同样成功:

BEGIN EXECUTE IMMEDIATE 'CREATE TABLE T_TEST_ANON (ID NUMBER)'; END;

涉及一个很多人会忽略的细节:匿名块属于调用者权限上下文,全局会话里的角色依然处于启用状态,因此角色里的UNLIMITED TABLESPACE依然有效。

3.4 定义者权限包:失败

接着建一个存储过程或者包:

CREATE OR REPLACE PACKAGE PKG_DEPLOY AS PROCEDURE RUN_CREATE_TABLE; END PKG_DEPLOY; / CREATE OR REPLACE PACKAGE BODY PKG_DEPLOY AS PROCEDURE RUN_CREATE_TABLE IS BEGIN EXECUTE IMMEDIATE 'CREATE TABLE T_TEST_PKG (ID NUMBER)'; END RUN_CREATE_TABLE; END PKG_DEPLOY; /

然后执行:

EXEC PKG_DEPLOY.RUN_CREATE_TABLE;

结果立刻复现:

ORA-01950: no privileges on tablespace 'TBS_APP_DATA' ORA-06512: at "APP_EXEC.PKG_DEPLOY", line 4 ORA-06512: at line 1

同一个用户,同一个CREATE TABLE,匿名块能过,命名包不能过。这就是典型到不能再典型的“奇怪 ORA-01950”。

3.5 再补一个对照组:直接授权后,包内立刻成功

为了解决疑问,把权限改成直接授予方式:

ALTER USER app_exec QUOTA UNLIMITED ON TBS_APP_DATA;

再次执行:

EXEC PKG_DEPLOY.RUN_CREATE_TABLE;

成功。关掉显式配额直接给系统权限也可以:

ALTER USER app_exec QUOTA 0 ON TBS_APP_DATA; GRANT UNLIMITED TABLESPACE TO app_exec;

包内同样能成功。

所以症结已经很清晰:角色在非匿名 PL/SQL 存储程序里“隐身”了。

4. 根因:definer-rights 存储程序默认“不认角色”

这条规则是 Oracle 安全模型的默认设计,不是 bug。

4.1 什么是定义者权限

默认情况下,Oracle 存储过程、函数、包、触发器都是以定义者权限(Definer Rights)运行的。也就是说,无论调用者是谁,过程体执行时都以创建者的身份和权限去解析 SQL、执行 DDL、访问对象。

在这个机制里,为了保证安全边界,数据库会做一个关键动作:暂时禁用当前会话中的角色,只保留那些直接授予给调用者的权限。

这个设计的理由很直白:角色通常是一批权限的集合,如果定义者权限的代码也能看见角色,那么一个普通用户只要拥有调用某个过程的权限,就可能“顺走”过程里用角色继承来的额外能力。比如过程里有一段动态 SQL 使用了CREATE ANY TABLE,而这个权限来自某个角色,那么任何能调用这个过程的人都可能间接获得这个能力,权限边界就失控了。

所以 Oracle 在存储程序的调用栈里会比较“强硬”地关闭角色识别。要注意的是,关闭的不是整个会话的角色,而是当前 PL/SQL 执行环境对“角色权限”的可见性。

4.2 一个通俗的类比

你可以把普通 SQL 会话想象成员工带着门禁卡进机房,门禁卡上绑定了多个权限组,所以整个楼层都能进。但进入某个重要设备间后,内部机械只认柜台上登记的“直接授权”,你身上那层“权限组”在系统眼里是不存在的。手动操作时你是“带卡的全能状态”,进入包体后你只剩“直接挂牌”的状态。

所以:

  • 手工执行CREATE TABLE,走的是会话级权限判断,角色生效,建表成功。
  • 进入PKG_DEPLOY这个定义者权限包后,角色被临时隐藏,UNLIMITED TABLESPACE等同于不存在,报ORA-01950。

4.3 不只是表空间配额会这样

这个坑并不只影响ORA-01950。凡是依赖角色继承来的系统权限,在定义者权限 PL/SQL 中做动态 SQL 时都可能失效:

  • CREATE ANY TABLE
  • DROP ANY TABLE
  • ALTER ANY INDEX
  • SELECT ANY TABLE
  • 等等

很多自动化运维工具为了图快,把角色当成权限发放的默认方式,最后在“工具调用存储过程执行 DDL”这种场景下就会连环踩雷。

4.4 为什么“权限已给”这么具有迷惑性

DBA 最初看到ORA-01950时,往往会去查DBA_TS_QUOTAS,发现确实没有显式配额,再一查用户又有UNLIMITED TABLESPACE权限,于是疑惑更大。关键在于,权限是否生效,和权限记录是否“存在”是两回事。

DBA_SYS_PRIVS里看不到通过角色继承的权限,这是正常的;DBA_ROLE_PRIVS里有角色记录,也是正常的;但如果在执行上下文里角色被禁用,那UNLIMITED TABLESPACE在运行时就是“不存在”的。

这次案例里,CREATE TABLE是直接授权的,所以建表 DDL 能过权限检查;但表空间配额检查发生在建立段的时候,它看到的不是“职责文件里写没写”,而是“当前执行上下文认不认”。权限记录看得见,运行时状态看不见,就成了“奇怪的 ORA-01950”。

5. 修复:三种改法,按架构精度取舍

针对这次的问题,有三条整改路径。没有绝对最优,取决于你的权限管控粒度。

5.1 方案一:把表空间配额变成用户级直接授权(最推荐)

直接给用户app_exec设置表空间配额,不再依赖角色传递。

如果这个用户自身 schema 的表就应该建在这个表空间上,最稳妥的做法是:

ALTER USER app_exec QUOTA UNLIMITED ON TBS_APP_DATA;

如果希望限制容量,可以精确到一个数值:

ALTER USER app_exec QUOTA 500M ON TBS_APP_DATA; ALTER USER app_exec QUOTA 500M ON TBS_OTHER;

这样权限从“角色+系统权限”变成了“用户+表空间绑定”,清清爽爽,也不会被存储过程的角色隐藏机制影响。

提示:显式配额是表空间级、用户级的对象关系,这种授权在定义者权限 PL/SQL 里依然有效,因为它不依赖角色。

这个方案对自动化部署最友好。业务方哪怕完全不懂 Oracle 安全模型,只要知道“这个用户在哪些表空间能建表”,就不会再出这种诡异问题。

5.2 方案二:直接授予 UNLIMITED TABLESPACE 给用户

如果建表目标表空间比较多,而且权限要求没那么严格,可以简单粗暴:

GRANT UNLIMITED TABLESPACE TO app_exec;

这个和显式配额一样属于直接授权,在定义者权限 PL/SQL 中依然有效。

但我不建议一上来就用它。UNLIMITED TABLESPACE意味着这个用户在所有表空间都不受限,一旦用户习惯不好,可能会把表建到SYSTEM、SYSAUX等地方,安全边界直接失去意义。能用显式配额解决,就用显式配额。

5.3 方案三:将包改为调用者权限 AUTHID CURRENT_USER

把包改成调用者权限,让过程执行时沿用调用者会话里的当前角色:

CREATE OR REPLACE PACKAGE PKG_DEPLOY AUTHID CURRENT_USER AS PROCEDURE RUN_CREATE_TABLE; END PKG_DEPLOY; / CREATE OR REPLACE PACKAGE BODY PKG_DEPLOY AUTHID CURRENT_USER AS PROCEDURE RUN_CREATE_TABLE IS BEGIN EXECUTE IMMEDIATE 'CREATE TABLE T_TEST_PKG (ID NUMBER)'; END RUN_CREATE_TABLE; END PKG_DEPLOY; /

注意,AUTHID CURRENT_USER不仅会影响角色可见性,还会影响未限定对象名的解析方式。包内部所有 SQL 引用都开始以“调用者 schema”为基准去解析。如果你的包原来依赖定义者拥有的表或视图,schema 一变,很可能会报ORA-00942: table or view does not exist。

所以这个方案严格来说不是“改一个关键字就完事”,而是动摇了整个代码执行上下文,需要重新做回归测试。一般只在无法变更用户权限、且包内部引用对象比较简单的场景下考虑。

5.4 方案四:把 DDL 从包里拆出来

如果这套自动化流程根本不需要通过 PL/SQL 执行 DDL,那更合理的设计是让部署平台直接以app_exec用户连接数据库,用 SQL*Plus 或 JDBC 执行脚本。普通会话默认启用角色,UNLIMITED TABLESPACE自然生效,问题直接消失。

这种调整涉及部署架构,但能从根本上避免“定义者权限包 + 动态 DDL”这种高危组合。

5.5 验证手段

改完之后,建议跑一遍完整的验证路径:

-- 1. 查看显式配额 SELECT * FROM DBA_TS_QUOTAS WHERE USERNAME = 'APP_EXEC'; -- 2. 查看直接系统权限 SELECT * FROM DBA_SYS_PRIVS WHERE GRANTEE = 'APP_EXEC'; -- 3. 查看角色继承 SELECT * FROM DBA_ROLE_PRIVS WHERE GRANTEE = 'APP_EXEC'; -- 4. 重新执行原包 EXEC PKG_DEPLOY.RUN_CREATE_TABLE; -- 5. 确认表对象已经建立 SELECT TABLE_NAME, TABLESPACE_NAME FROM DBA_TABLES WHERE OWNER = 'APP_EXEC' AND TABLE_NAME = 'T_TEST_PKG';

如果第四条能成功,第五条能查到表落在TBS_APP_DATA,问题就闭环了。

6. 同错不同因:另外几种“看起来有权限却失败”的常见现场

ORA-01950不是只有“存储过程里角色被禁用”这一种奇怪玩法。下面这几种场景我也在不同项目里见过,表现都是“权限检查没问题,实际建表失败”,但根因各不相同。

6.1 默认表空间和配额对不上

用户有好几个表空间的配额,但创建表时没有显式写TABLESPACE子句。按照规则,Oracle 会把段建到用户的默认表空间里,如果默认表空间恰好没有任何配额,即便其他表空间配额充足,一样会报ORA-01950。

排查顺序很简单:

-- 看默认表空间 SELECT USERNAME, DEFAULT_TABLESPACE FROM DBA_USERS WHERE USERNAME = 'APP_EXEC'; -- 看所有表空间配额 SELECT * FROM DBA_TS_QUOTAS WHERE USERNAME = 'APP_EXEC';

如果DEFAULT_TABLESPACE = TBS_OTHER,而配额全在TBS_APP_DATA,那就需要明确建表目标:

CREATE TABLE T_TEST_TMP (ID NUMBER) TABLESPACE TBS_APP_DATA;

或者直接把默认表空间和配额对齐。

6.2 大小写/引号表空间名不一致

这个坑主要出在手工创建的“大小写敏感表空间”上。Oracle 默认把未加引号的标识符转为大写,如果建表空间时用了带引号的混合大小写名称,比如:

CREATE TABLESPACE "Apps_Data" DATAFILE '/u01/app/oracle/oradata/ORCL/apps_data01.dbf' SIZE 100M;

后续写配额时写成大写APPS_DATA,就可能出现“权限名对不上”的深层问题。虽然通常直接报ORA-00959: tablespace 'APPS_DATA' does not exist,但插件式架构下偶尔也会以权限异常方式呈现。

处理方式是统一命名规范:表空间名一律不带引号,让数据库统一转为大写,后续所有授权和 DDL 都按大写写法执行。

6.3 12c 以后 CDB/PDB 权限隔离

12c 以后,表空间配额是容器级别的,不能在 CDB$ROOT 和 PDB 之间混用。普通本地用户的管理通常发生在 PDB 内部,如果你习惯在 CDB$ROOT 里做ALTER USER ... QUOTA,授权很可能没有落到实际使用的 PDB 里。

排查时先确认当前容器:

SHOW CON_NAME;

然后在正确的 PDB 容器下执行授权,例如:

ALTER SESSION SET CONTAINER = ORCLPDB1; ALTER USER app_exec QUOTA UNLIMITED ON TBS_APP_DATA;

很多人从 11g 升到 19c 后容易踩到这一条,因为所见“用户”存在,查权限也在,但就是不在同一个容器内。

6.4 默认角色被关闭

另一种隐藏情况是,角色确实授给了用户,但通过ALTER USER ... DEFAULT ROLE NONE或者DEFAULT ROLE ALL EXCEPT ...把角色从“默认启用列表”里剔除了。这种情况下,用户一登录,角色就不会自动启用,UNLIMITED TABLESPACE在普通会话里都不生效,更不用说存储过程。

检查命令:

-- 查用户默认角色情况 SELECT * FROM DBA_ROLE_PRIVS WHERE GRANTEE = 'APP_EXEC'; -- 查当前会话里实际的启用角色 SELECT * FROM SESSION_ROLES;

如果确认角色被默认禁用,直接启用:

ALTER USER app_exec DEFAULT ROLE ALL;

这类问题通常伴随着“之前还能建表,现在突然不行,而且没有做任何权限变更”的现象,因为角色默认状态的调整往往在系统维护文档里被一笔带过。

6.5 常见场景汇总

场景核心疑惑点快速诊断推荐处理
定义者权限包内 DDL手工成功、包内失败关注SESSION_ROLES与存储程序上下文显式配额或直接授权
默认表空间无配额其他表空间有配额对比DBA_USERS与DBA_TS_QUOTAS修正默认表空间或显式指定目标表空间
表空间名大小写混乱授权语句看不出问题检查DBA_TABLESPACES原始名称统一命名规范
CDB/PDB 容器错位用户存在但授权不带入 SQL 会话先SHOW CON_NAME再排查DBA_TS_QUOTAS在正确容器内授权
角色默认不启用角色记录存在但会话里失效查SESSION_ROLES/DBA_ROLE_PRIVS调整DEFAULT ROLE设置

7. 这次排查给我留下的几个习惯

这件事之后,我在每次处理类似权限问题时都会多问自己一句:现在看到的是“数据字典里的权限”,还是“当前执行上下文真正生效的权限”?

后来我也在团队里定了一套排查ORA-01950的固定动作,按顺序执行,基本不会漏:

  1. 先看DBA_USERS里的默认表空间和账户状态。
  2. 再看DBA_TS_QUOTAS里有没有显式配额。
  3. 然后查DBA_SYS_PRIVS和DBA_ROLE_PRIVS,确认UNLIMITED TABLESPACE是直接授予还是角色继承。
  4. 再查ROLE_SYS_PRIVS,确认角色内部到底装了什么。
  5. 最后查SESSION_ROLES,只有当前会话里实际启用的角色才对运行时判断有影响。

如果用户是通过 PL/SQL 包执行 DDL,还要额外确认包到底是不是定义者权限,以及包内 SQL 是否有未限定的对象引用。

还有一个更实际的心得:权限治理上,尽量把“建表能力”做成用户和表空间的直接绑定,而不是把UNLIMITED TABLESPACE这种万能权限塞进角色里到处挂载。角色适合批量分发“通用功能权限”,但表空间配额这种高度依赖执行环境的能力,越直白越好排错。

这次故障最后只改了一行授权语句就解决了,但排查过程带出来的认知,比那一行语句值钱得多。以后再看到ORA-01950: no privileges on tablespace,你至少能多想一层:权限记录有,和权限在运行时被认,是两码事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询