
在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 usernameAPP_USER;结果正常默认表空间就是 USERS临时表空间是 TEMP。再查配额SELECT * FROM dba_ts_quotas WHERE usernameAPP_USER;有记录且max_bytes -1也就是无限配额。到这里常规路径已经走不通了因为理论上无限配额足以让用户在 USERS 上创建表。接下来查系统权限SELECT * FROM dba_sys_privs WHERE granteeAPP_USER;结果里有 CREATE SESSION、CREATE TABLE、CREATE PROCEDURE 等但确实没有UNLIMITED TABLESPACE。于是我怀疑权限是通过角色间接授予的继续看角色授权SELECT * FROM dba_role_privs WHERE granteeAPP_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。例如过程属于 OWNEROWNER 通过 ROLE_ADMIN 获得了UNLIMITED TABLESPACECREATE 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$SQLSELECT 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$SESSIONSELECT 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把“角色是否默认启用”这个点排查一遍。权限不生效往往比没有权限更隐蔽也更值得复盘。这个案例后来我补了一个定时脚本每周扫一次用户默认角色和表空间配额避免同类问题在下一套环境里再次爆炸。