在Oracle运维中,ORA-01950: no privileges on tablespace这个错误,绝大多数时候三句话就能解释完:用户没有配额,加一句ALTER USER ... QUOTA UNLIMITED ON ...;或者缺UNLIMITED TABLESPACE权限,补一条GRANT完事。但我最近排查的一个现场,把这两招轮着用了一遍,错误还是顽固存在。问题不复杂,但藏得比较深,值得专门写一篇复盘。这篇文章会把 ORA-01950 背后的表空间配额、权限角色、默认角色机制串起来讲,也会给出可以直接抄的排查SQL。不管是刚接触数据库的运维新人,还是被这个错误反复折磨的应用开发,都能在五分钟左右定位到根因。
1. ORA-01950到底是什么:先搞清楚表空间配额的机制
1.1 错误信息的直译与触发场景
ORA-01950 的字面意思是“在表空间上没有权限”。这个说法其实有点抽象,因为它不是说你没有CREATE TABLE这种权限,而是说你在某个表空间上没有“租用空间”的权利。数据库在创建段对象时需要分配空间,分配之前会检查用户有没有资格。
触发这个错误的典型操作包括:
CREATE TABLE t (id NUMBER) TABLESPACE tbs;CREATE INDEX idx ON t(col) TABLESPACE tbs;ALTER TABLE t MOVE TABLESPACE tbs;ALTER INDEX idx REBUILD TABLESPACE tbs;- 在线重定义、添加分区、创建 LOB 段等任何需要在表空间内分配区(extent)的动作
很多人第一次遇到 ORA-01950 是在新环境里创建测试用户之后。比如新建了用户 TEST01,只给了CREATE SESSION和CREATE TABLE,然后执行:
CREATE TABLE TEST01.T (id NUMBER);此时数据库会尝试把段对象建在 TEST01 的默认表空间 USERS 上。用户没有UNLIMITED TABLESPACE权限,而且 ADMIN 也没给 TEST01 在 USERS 上设置任何配额,于是直接报错。这时候去查DBA_TS_QUOTAS,根本看不到 TEST01 的记录。
这里要先澄清一个容易混淆的概念:配额为 0 和没有配额记录,效果类似但略有区别。如果你执行过ALTER USER TEST01 QUOTA 0 ON USERS;,那么配额记录是存在的,但大小是 0;如果从来没执行过任何配额相关命令,DBA_TS_QUOTAS里查不到这条用户记录。两种情况下创建段对象都会触发 ORA-01950。
另外,配额超限是另一个错误 ORA-01536: space quota exceeded for tablespace。只有当你已经拥有配额、但分配的空间超过限额时才会出现它。ORA-01950 的根因更前置,是压根没有获得在这个表空间内分配空间的许可。
1.2 配额与UNLIMITED TABLESPACE的权限层级
Oracle 的思路很明确:表空间配额是用户属性,UNLIMITED TABLESPACE是系统权限。两条路都能让你合法使用表空间,但底层逻辑不同。
配额是精确到表空间的限制,例如:
ALTER USER TEST01 QUOTA 100M ON USERS; ALTER USER TEST01 QUOTA UNLIMITED ON USERS;第一条表示 TEST01 只能在 USERS 表空间里使用最多 100MB 空间;第二条表示可以无限使用 USERS 表空间,但只限 USERS。配额可以针对不同表空间分别设置,这在多用户共享数据库时非常有用。
UNLIMITED TABLESPACE则是一个系统权限,一旦授予,用户可以绕过所有表空间上的配额限制,在所有永久表空间里分配空间:
GRANT UNLIMITED TABLESPACE TO TEST01;这个权限通常包含在DBA角色里,所以拥有 DBA 角色的管理员很少遇到 ORA-01950。但不要误以为给了RESOURCE角色就能解决配额问题。RESOURCE 角色主要携带 CREATE TABLE、CREATE PROCEDURE、CREATE SEQUENCE 等一系列对象创建权限,并不自动包含UNLIMITED TABLESPACE。很多新人在空库上创建用户后,执行GRANT CONNECT, RESOURCE TO user;,然后建表照样报 ORA-01950,原因就在这儿。
在权限规划上,我建议把这两件事分开看待:
- 临时排查:直接补
QUOTA UNLIMITED ON 具体表空间影响面最小; - 长期使用:按业务容量规划,设置精确配额;
- 特殊场景:如果应用确实需要在多个表空间创建对象,再考虑授予
UNLIMITED TABLESPACE,但要清楚它是一把能打开所有门的总钥匙。
2. 这次排障现场的详细复盘:为什么“常规解法”失效
2.1 现场表象:权限查了又查,该有的权限全都有
这套环境是 Oracle 11.2.0.4 的测试库,应用账号 APP_USER 负责夜间批量任务,任务脚本里有大量临时表的创建操作。某天开始,批量日志里连续出现 ORA-01950: no privileges on tablespace 'USERS'。
按照“经验解”,我第一时间执行:
ALTER USER APP_USER QUOTA UNLIMITED ON USERS;执行成功,然后让业务重新跑任务,结果仍然报错。这时候我意识到不是简单的配额缺失。
继续翻元数据:
SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username='APP_USER';结果正常,默认表空间就是 USERS,临时表空间是 TEMP。
再查配额:
SELECT * FROM dba_ts_quotas WHERE username='APP_USER';有记录,且max_bytes = -1,也就是无限配额。到这里常规路径已经走不通了,因为理论上无限配额足以让用户在 USERS 上创建表。
接下来查系统权限:
SELECT * FROM dba_sys_privs WHERE grantee='APP_USER';结果里有 CREATE SESSION、CREATE TABLE、CREATE PROCEDURE 等,但确实没有UNLIMITED TABLESPACE。于是我怀疑权限是通过角色间接授予的,继续看角色授权:
SELECT * FROM dba_role_privs WHERE grantee='APP_USER';果然,APP_USER 拥有 DBA 角色,还有两个业务角色 APP_ROLE 和 JOB_ROLE。
到这里产生了新的矛盾:有 DBA 角色的人,理论上会话里会带上UNLIMITED TABLESPACE,怎么会报 ORA-01950?除非 DBA 角色根本没有在当前会话里被启用。
2.2 真正的“元凶”:默认角色被禁用
为了验证“角色未启用”这个猜测,我让应用账号重新登录后,在当前窗口执行:
SELECT * FROM session_roles;结果令同事很意外:会话里只有 CONNECT、APP_ROLE、JOB_ROLE,没有 DBA。也就是说,这个用户虽然被授权了 DBA 角色,但登录时 DBA 角色没有加载进来,UNLIMITED TABLESPACE权限自然没有生效。
往上追溯变更记录,发现前任 DBA 在整改账号权限时,执行过一条指令:
ALTER USER APP_USER DEFAULT ROLE CONNECT, APP_ROLE, JOB_ROLE;目的是收紧权限,避免应用账号拥有 DBA 角色。这个操作本身没问题,但它把 DBA 角色从默认角色里踢了出去。Oracle 在用户建立会话时,只会自动启用被标记为“默认角色”的角色,非默认角色必须手动通过SET ROLE打开。于是应用重连后,DBA 角色一直处于未启用状态,UNLIMITED TABLESPACE权限等于不存在。
修复方式也很简单:
ALTER USER APP_USER DEFAULT ROLE ALL;这样所有已授予的角色又会默认启用。但考虑到安全,一个应用账号长期持有 DBA 角色本来就不是好方案,所以我们最终把 DBA 角色收回,改为显式授予最小权限:
REVOKE DBA FROM APP_USER; GRANT UNLIMITED TABLESPACE TO APP_USER;应用重新连接后,批量任务恢复正常。
事后复盘,这个案例“奇怪”在传统排障习惯上。多数人遇到 ORA-01950,只会看DBA_USERS、DBA_TS_QUOTAS、DBA_SYS_PRIVS。权限一旦放在角色里,直接查用户系统权限是看不到的。必须再往下看角色授权,以及会话里实际生效的角色,才能找到真因。
3. 从底层理解权限校验:几个高频“奇怪”诱因
3.1 角色授权与直接授权的行为差异
Oracle 的权限体系里,直接授权给用户和通过角色授权,在会话中的生效方式有明显差异。直接授权是立即全局生效的,不管你怎么切换默认角色、怎么SET ROLE,只要用户账户拥有这项权限,会话里就有。权限通过角色授予时,必须先启用对应角色,权限才会注册到当前会话。
默认情况下新建用户,所有已授予角色都会被标记为默认角色,所以很多管理员长期感知不到这个差异。一旦有人执行过ALTER USER ... DEFAULT ROLE ALL EXCEPT ...或者DEFAULT ROLE NONE,再或者为角色设置了密码、应用没提供角色密码时,角色权限就会“静默失踪”。
另外还有一类隐藏场景:存储过程权限上下文。定义者权限存储过程(AUTHID DEFINER)在执行时,使用的是过程所有者的权限集合,并且不会自动启用通过角色授予的权限。如果你把所有表空间相关权限都挂在角色上,然后让存储过程里执行动态 SQL 建表,即使调用者看上去权限齐全,实际执行时也可能报 ORA-01950。
例如,过程属于 OWNER,OWNER 通过 ROLE_ADMIN 获得了UNLIMITED TABLESPACE:
CREATE OR REPLACE PROCEDURE owner.p_create_tab AUTHID DEFINER AS BEGIN EXECUTE IMMEDIATE 'CREATE TABLE owner.t(id NUMBER) TABLESPACE users'; END;当其他用户调用这个过程时,数据库按 OWNER 的权限检查建表操作。如果UNLIMITED TABLESPACE只存在于 ROLE_ADMIN 这个角色上,而定义者权限过程中该角色未被激活,就会遇到 ORA-01950。修复思路是把这类关键权限直接授予过程所有者,或者改用AUTHID CURRENT_USER。
3.2 多租户架构下CDB/PDB配额被隔离
从 12c 开始,多租户架构也给 ORA-01950 增加了新的迷惑点。CDB 里的公共用户(C##开头的用户)虽然在根容器里可能配好了权限,但配额是基于具体容器单独计算的。你在 CDB$ROOT 给 C##APP_USER 设置了 USERS 表空间的无限配额,不代表 PDB 里也自动有同样的配额。
典型的报错场景是:应用连的是 PDB1,用户 C##APP_USER 在 PDB1 里创建表,报 ORA-01950。管理员跑到 CDB 里查DBA_TS_QUOTAS,看到配额明明是 UNLIMITED,非常困惑。其实需要先切换容器:
ALTER SESSION SET CONTAINER = PDB1; ALTER USER C##APP_USER QUOTA UNLIMITED ON USERS;排查时可以一次看全容器内的配额情况:
SELECT con_id, username, tablespace_name, max_bytes FROM cdb_ts_quotas WHERE username = 'C##APP_USER' ORDER BY con_id;另外,公共用户的默认表空间也要在每个容器内分别确认。一个容器里正常,不代表另一个容器里正常。
3.3 大小写敏感表空间:隐形的一刀
还有一个不算高频、但遇到了就会让人挠头的情况:表空间命名用了双引号,导致大小写敏感。比如有人建表空间时这么写:
CREATE TABLESPACE "App_Data" DATAFILE '/u01/oracle/data/app01.dbf' SIZE 100M;此时真实表空间名是App_Data,不是APP_DATA。应用端写 DDL 时如果没带双引号:
CREATE TABLE t (id NUMBER) TABLESPACE App_Data;Oracle 会默认把标识符转成大写APP_DATA去匹配,结果找不到配额,甚至找不到表空间。轻则报 ORA-01950,重则报 ORA-00959: tablespace 'APP_DATA' does not exist。
检查时别只盯着屏幕上的错误文本,直接落到数据字典:
SELECT tablespace_name FROM dba_tablespaces;如果表空间名里大小写混着,大概率是当初用双引号创建的。这种名字看着难受,改起来也不容易,因为所有关联到它的 SQL 都得保持一致。我的建议是能不用双引号建表空间就不用,已经用了的,把所有 DDL 都按精确大小写加双引号处理,而不是靠肉眼猜。
4. 手把手排查流程:5分钟定位ORA-01950到底卡在哪
4.1 检查权限的SQL脚本合集
我把这次排障用到的检查脚本整理成一套,可以直接在数据库里按顺序跑。假设业务用户是 APP_USER:
-- 1. 确认当前会话身份和对象归属 SELECT SYS_CONTEXT('USERENV','SESSION_USER') AS session_user, SYS_CONTEXT('USERENV','CURRENT_SCHEMA') AS current_schema FROM dual; -- 2. 用户默认表空间、临时表空间 SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username = UPPER('APP_USER'); -- 3. 表空间配额记录 SELECT tablespace_name, max_bytes, CASE WHEN max_bytes = -1 THEN 'UNLIMITED' ELSE TO_CHAR(max_bytes) END AS max_size FROM dba_ts_quotas WHERE username = UPPER('APP_USER'); -- 4. 直接授予的系统权限 SELECT privilege, admin_option FROM dba_sys_privs WHERE grantee = UPPER('APP_USER') ORDER BY privilege; -- 5. 通过角色间接获得的系统权限 SELECT r.granted_role, p.privilege FROM dba_role_privs r JOIN dba_sys_privs p ON p.grantee = r.granted_role WHERE r.grantee = UPPER('APP_USER') ORDER BY r.granted_role, p.privilege; -- 6. 当前会话实际启用的角色 SELECT * FROM session_roles; -- 7. 当前会话里与TABLESPACE相关的权限是否真正生效 SELECT * FROM session_privs WHERE privilege LIKE '%TABLESPACE%';第 5 步是关键补充。如果只执行第 4 步,你会误以为用户没有任何UNLIMITED TABLESPACE权限,因为用户自己名下的系统权限确实没有。但通过 DBA 或者自定义角色授权后,第 5 步会把这些隐藏关系暴露出来。
第 6、7 步则回答了“权限是否存在”和“权限是否生效”的区别。DBA_ROLE_PRIVS表示用户被授予了哪个角色;SESSION_ROLES表示当前会话真正启用了哪个角色。两者不一致,就是角色默认设置或SET ROLE使用不当导致的。
比较理想的输出应该是:在第 3 步看到配额记录(或第 7 步看到 UNLIMITED TABLESPACE),且第 6 步显示持有对应角色的会话正常。如果第 3 步为空、第 4 步为空、第 5 步为空,那就是权限彻底缺失,补授权就行。
4.2 按照报错对象的实际归属去追
另一个排查盲区是“你以为报错的用户,不一定是真正执行 DDL 的用户”。常见于连接池和存储过程混合使用的场景。
比如应用配置里写的是 APP_USER 登录,但批量任务里有一段动态 SQL 调用了另一个 schema 下的存储过程。存储过程以定义者权限执行,实际建表用户是 APP_OWNER。APP_USER 有配额没用,APP_OWNER 没有配额,就会报 ORA-01950,而且错误会体现在任务日志里,容易让人误判是 APP_USER 的问题。
快速确认当前连接身份,可以用前面第 1 组 SQL。如果想看已经执行完的 SQL 是谁解析的,可以查V$SQL:
SELECT sql_id, parsing_schema_name, sql_text FROM v$sql WHERE sql_text LIKE '%CREATE TABLE%' ORDER BY last_active_time DESC;其中parsing_schema_name才是真正执行 DDL 的解析用户。肉眼判断时经常会犯的错,是拿连接用户名去套对象属主。所以在给某个用户加配额之前,先想清楚:这个表最终会落在哪个 schema 下面?
如果涉及多个数据库实例,还可以查V$SESSION:
SELECT sid, serial#, username, program, machine FROM v$session WHERE type != 'BACKGROUND';把连接来源、应用进程和用户名对上,能减少很大的排查成本。
4.3 临时救火与长期修复
如果是生产系统正在报错,先恢复业务再说。临时方案一般两个选一个:
ALTER USER APP_USER QUOTA UNLIMITED ON USERS; -- 或者 GRANT UNLIMITED TABLESPACE TO APP_USER;两种方式差别在于影响范围。前者只解决一个表空间,后者解决所有表空间。实际操作中,我建议优先用前者,避免因为临时救火把权限放得过大,事后忘记回收。
长期修复要从权限规划角度做几件事:
- 明确每个业务账号默认表空间,并为它规划定额;
- 禁止应用账号直接持有 DBA 角色,尽量不用
UNLIMITED TABLESPACE这种全局权限; - 在发布流程中新增一个检查项,用脚本扫描新账号是否缺少配额;
- 对已存在的账号做定期巡检,输出权限矩阵。
巡检脚本可以写得很简单:
SELECT u.username, u.default_tablespace, NVL(q.max_bytes, 0) AS quota_bytes FROM dba_users u LEFT JOIN dba_ts_quotas q ON q.username = u.username AND q.tablespace_name = u.default_tablespace WHERE u.account_status = 'OPEN' AND u.username NOT IN ('SYS','SYSTEM','OUTLN','XDB') ORDER BY u.username;把没有配额记录或配额为 0 的账号列出来,结合业务紧急度逐批处理。
5. 来自故障现场的额外提醒:ORA-01950的兄弟姐妹
5.1 容易混淆的错误对比
ORA-01950 只是表空间权限问题里的一个代表,日常还会碰到长得相似、原因完全不同的错误。我把容易混淆的几条整理成了一张速查表:
| 错误码 | 报错含义 | 与ORA-01950的区别 | 常见处理 |
|---|---|---|---|
| ORA-01950 | 表空间上无权限 | 无配额记录,或UNLIMITED TABLESPACE权限未生效 | 补配额/授权,检查默认角色 |
| ORA-01536 | 表空间配额超限 | 已有配额,但分配空间超过限制 | 增大配额或清理段 |
| ORA-00959 | 表空间不存在 | 名称写错或大小写不匹配 | 修正表空间名 |
| ORA-01647 | 表空间只读 | 表空间本身只读,禁止写入 | 解除只读 |
| ORA-01650 | 无法扩展段 | 表空间空间不足或数据文件达上限 | 扩展数据文件 |
| ORA-01031 | 权限不足 | 通用权限不足,范围更广 | 查DBA_SYS_PRIVS定位缺失权限 |
排障时,先看错误码再动手。ORA-01950 重点是配额和UNLIMITED TABLESPACE权限;ORA-01536 重点已经是“超限”了;ORA-01647 哪怕你有无限配额也写不进去,因为表空间级别被锁死了。这些区别能够帮你少走很多弯路。
5.2 应用端容易踩的坑:在CREATE TABLE里硬写TABLESPACE
还有一类 ORA-01950 是应用自己埋的雷,和数据库权限设计无关。很多开发团队在代码里写死了表空间:
CREATE TABLE order_tmp ( id NUMBER, order_no VARCHAR2(32) ) TABLESPACE data01;开发环境里 data01 存在,也给了配额,一切正常。到了生产环境,表空间叫 DATA01_BIG,应用没改代码,生产库里找不到这个表空间,或者找到了但没给业务用户配额,于是建表任务报 ORA-01950,或者前面的 ORA-00959。
我的建议是:DDL 里的 TABLESPACE 子句尽量交给数据库侧来控制。用户有默认表空间,就让它落到默认表空间上;实在需要指定表空间,就在发布配置里面做成环境变量,不要写死在 SQL 文件里。上线检查时,用数据字典对比开发和生产的表空间命名,能发现大部分问题:
SELECT TABLESPACE_NAME FROM DBA_TABLESPACES ORDER BY 1;不管 DDL 是手工执行还是由 CI 流程下发,都值得把这条查询纳入发布前的比对脚本。
最后分享一个我这些年养成的习惯:凡是建表报 ORA-01950,第一件事不是急着ALTER USER加配额,而是先看一眼SESSION_ROLES和DBA_ROLE_PRIVS,把“角色是否默认启用”这个点排查一遍。权限不生效往往比没有权限更隐蔽,也更值得复盘。这个案例后来我补了一个定时脚本,每周扫一次用户默认角色和表空间配额,避免同类问题在下一套环境里再次爆炸。