尧图精选

ORA-01950不是权限问题?表空间配额与角色继承陷阱排查指南

🕒 发布时间:2026/10/2 20:16:44 📁 来源:尧图网络
先说结论ORA-01950 这个报错名字里虽然写着 no privileges没有权限但在绝大多数情况下它根本不是“权限”问题而是表空间配额tablespace quota问题。这是我排过很多次数据库故障之后最想强调的一点。事情的起因是某天早上开发在群里贴了一条报错ORA-01950: no privileges on tablespace USERS开发说账号权限都给了为什么还报错我当时的第一反应是查 DBA_SYS_PRIVS结果发现 UNLIMITED TABLESPACE 这个权限确实在再往下一层查角色关系发现它是通过角色带进来的。这才是整件事“奇怪”的根源——权限在数据字典里清清楚楚但 Oracle 在分配表空间空间的时候偏偏不认角色带来的 UNLIMITED TABLESPACE。这篇文章就把这次排查的完整过程摊开讲顺便把 ORA-01950 背后那些容易踩进去的坑一起整理出来。无论是刚上手的新人还是被类似问题折腾过的老手顺着这条思路走基本都能在几分钟内定位到根因。1. 当“有权限”变成没权限现象还原与直觉排查1.1 一条报错信息背后的表空间权限模型ORA-01950 的机制其实很简单当一个用户尝试在某个表空间创建对象表、索引、LOB 段等或者执行 ALTER TABLE MOVE、SHRINK SPACE 这类操作时Oracle 要向表空间申请存储空间。在分配空间之前Oracle 会检查这个用户有没有“资格”在该表空间使用空间。这个“资格”有两种来源用户在某个表空间上有配额quota比如 ALTER USER app_user QUOTA 100M ON users用户拥有 UNLIMITED TABLESPACE 系统权限。注意关键在于CREATE TABLE 权限本身和表空间配额完全是两码事。你可以有 CREATE TABLE 权限但如果没有配额照样建不出表。就好比你有了驾驶证但没买停车位车开过去还是停不了。很多开发人员甚至部分 DBA 都容易把这两者混在一起。还有一个更隐蔽的点用户设置了 DEFAULT TABLESPACE并不代表默认表空间就自动有配额。默认表空间只是指定“建表时不写 TABLESPACE 子句就放这里”配额仍然需要单独授予。这一点被误解的次数非常多。1.2 三步定位配额、系统权限、角色路径遇到 ORA-01950 时我的排查顺序固定是三步基本不浪费时间第一步看用户的基本信息包括默认表空间和临时表空间SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username UPPER(USERNAME);第二步看用户在各表空间的配额。如果查询结果为空说明用户在所有表空间上都没有配额SELECT tablespace_name, username, max_bytes, blocks FROM dba_ts_quotas WHERE username UPPER(USERNAME);第三步看用户直接拥有的系统权限SELECT privilege, grantee, admin_option FROM dba_sys_privs WHERE grantee UPPER(USERNAME);这三步做完大部分 ORA-01950 都能定位到答案要么没配额要么没 UNLIMITED TABLESPACE。但问题就出在第三步这里——如果 UNLIMITED TABLESPACE 不是直接授予用户而是通过角色继承来的单纯查 DBA_SYS_PRIVS 会漏掉它。这就是我这次遇到的情况也是标题里“奇怪”二字的由来。2. 最阴的那个坑UNLIMITED TABLESPACE 从角色继承不生效2.1 为什么数据字典显示有权限Oracle却不认我排查的那个账号在 DBA_SYS_PRIVS 里确实查不到 UNLIMITED TABLESPACE但开发坚持说“权限给过了”。于是我又查了角色关系SELECT rp.granted_role, sp.privilege FROM dba_role_privs rp LEFT JOIN role_sys_privs sp ON sp.role rp.granted_role WHERE rp.grantee UPPER(USERNAME) AND sp.privilege UNLIMITED TABLESPACE;结果出来了用户确实被授予了一个角色而这个角色里包含了 UNLIMITED TABLESPACE。从当前会话视角看连 SESSION_PRIVS 里都能查到 UNLIMITED TABLESPACE 这个权限。那为什么建表还是报 ORA-01950因为 Oracle 在表空间配额检查这个环节有一个特殊处理通过角色授予的 UNLIMITED TABLESPACE 不会在处理空间分配时被承认。说白了你拿着工牌照片进不了门禁必须用卡刷。权限虽然挂在会话上但分配空间这段逻辑只看“直接授权”的来源。这种现象在官方文档和 MOS 里都有对应说明但日常运维中很容易被忽略。因为从表面看“有权限”是事实“不能建表”也是事实两者同时出现就显得很诡异。我后来还专门用一个测试账号复现过确认它在 19c 环境下依然如此。2.2 权限来源排查SQL清单所以遇到“奇怪”的 ORA-01950一定要把“角色路径”也查一遍。下面这一组 SQL 可以直接保存成常用的排查脚本-- 用户被授予了哪些角色 SELECT grantee, granted_role, admin_option, default_role FROM dba_role_privs WHERE grantee UPPER(USERNAME); -- 这些角色里包含哪些系统权限重点看 UNLIMITED TABLESPACE SELECT role, privilege, admin_option FROM role_sys_privs WHERE role IN ( SELECT granted_role FROM dba_role_privs WHERE grantee UPPER(USERNAME) ) AND privilege UNLIMITED TABLESPACE; -- 当前会话里实际生效的权限确认 UNLIMITED TABLESPACE 是否可见 SELECT privilege FROM session_privs WHERE privilege UNLIMITED TABLESPACE;注意DBA_ROLE_PRIVS 能查出来的前提是当前账号有访问数据字典的权限。如果你手头只有业务账号也可以先查 USER_ROLE_PRIVS 和 SESSION_PRIVS用当前会话的视角验证问题。2.3 用最少步骤复现一次如果看了上面的解释还觉得不够直观建议直接做一次迷你实验。整个过程不超过两分钟非常适合用来向开发同事证明“这不是权限没给而是给法不对”。用 DBA 权限执行下面的操作CREATE USER test_quota IDENTIFIED BY test_quota DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp; CREATE ROLE r_test_quota; GRANT UNLIMITED TABLESPACE TO r_test_quota; GRANT r_test_quota TO test_quota; GRANT CREATE SESSION, CREATE TABLE TO test_quota;然后切换到 test_quota 连接执行CREATE TABLE t_test (id NUMBER);大概率会得到ORA-01950: no privileges on tablespace USERS这时候再用 DBA 执行一次直接授权GRANT UNLIMITED TABLESPACE TO test_quota;再回到 test_quota 会话重跑 CREATE TABLE表就建成功了。这个实验对理解“角色来源权限不参与配额检查”非常有帮助。3. 比配额更隐蔽的触发场景3.1 默认表空间被改到 SYSTEM 的坑有一种场景很多 DBA 都见过应用账号建表时报 ORA-01950查了 DBA_TS_QUOTAS用户在 USERS 表空间明明有配额为什么还报错这时候要看 DBA_USERS.DEFAULT_TABLESPACE。如果默认表空间被设置成了 SYSTEM而且用户没有 UNLIMITED TABLESPACE 权限那么建表时不管有没有其他表空间的配额都会触雷。因为 SYSTEM 表空间不走“用户配额”这套机制普通用户要在 SYSTEM 上创建对象必须依赖 UNLIMITED TABLESPACE 权限。这种情况常见于早期建账号时没指定 DEFAULT TABLESPACE导致系统默认落到了 SYSTEM 上。处理办法很简单ALTER USER app_user DEFAULT TABLESPACE users;改完之后再让应用用新会话重试通常问题立刻消失。如果是已经建出来的对象在 SYSTEM 上还得考虑后续迁移但那是另一个工程了。3.2 建表时显式指定了没有配额的表空间还有一种“看起来毫无道理”的情况应用账号在 USERS 表空间建表正常但在 TBS_APP 表空间建表就报 ORA-01950。这种问题往往藏在建表语句里。比如CREATE TABLE t_log ( id NUMBER, msg VARCHAR2(2000) ) TABLESPACE tbs_app;如果用户对 TBS_APP 没有配额也没有 UNLIMITED TABLESPACE 直接权限就会在对象创建时被拒绝。应用侧经常把表空间名硬编码在脚本里环境切换时表空间名一变问题就冒出来了。排查方法很简单把报错里的表空间名捞出来对应用户的 DBA_TS_QUOTAS 里查一下就知道。处理方式也很直接要么给这个表空间加配额要么把建表语句里的 TABLESPACE 子句改成用户有配额的表空间。3.3 CLOB / BLOB 字段带来额外段需求这个坑我印象特别深。曾经有开发反馈一张只有几十条记录的小表一加 CLOB 字段就报 ORA-01950不加就正常。原因在于 Oracle 里的大对象字段CLOB/BLOB并不仅仅在表的同一个段里存数据它还有独立的 LOB 段。LOB 段同样要占表空间空间同样受用户配额约束。如果表段空间没超配额但 LOB 段的额外空间需求把配额打满或用户本身没有对应表空间的配额就会触发 ORA-01950。而且这里有个容易误判的点表很小不代表 LOB 段不需要空间。即使 CLOB 字段里没存多少数据LOB 段在创建时也要分配初始 extent。配额的统计是按段来算的不是按账面看起来有几行数据。处理方式有两种一是给对应的表空间增加配额二是在建表时为 LOB 单独指定一个用户有充足配额的表空间CREATE TABLE t_article ( id NUMBER PRIMARY KEY, content CLOB ) TABLESPACE users LOB(content) STORE AS (TABLESPACE users);遇到类似报错千万别忘了把对象类型往 LOB 段、索引段这些“隐藏段”上想一想。3.4 存储过程与动态SQL的权限切换还有一类少见的坑来自存储过程的权限模型。Oracle 的存储过程默认是定义者权限AUTHID DEFINER执行时用的是存储过程属主的权限。但如果存储过程定义成了调用者权限AUTHID CURRENT_USER里面又用了动态 SQL 建表那空间分配检查就会基于调用者的身份来判断。举个例子一个属主是 DBA 账号的存储过程用 AUTHID CURRENT_USER 创建存储过程里面动态执行 CREATE TABLE。普通应用账号去调用时Oracle 检查的是“应用账号”有没有目标表空间的配额而不是 DBA 账号有没有。结果应用账号手动建表是正常的一调用存储过程就报 ORA-01950。这种错很容易让排查方向跑偏。遇到“手动能做跑程序才报错”的情况建议先看存储过程的 AUTHID 类型再看调用者和定义者在目标表空间上的配额差异。4. 从救火到长期防范操作模板与管理办法4.1 正确的授权操作与常见误区先说结论普通应用账号不要动不动就扔一个 UNLIMITED TABLESPACE 给它虽然这能一劳永逸解决所有配额问题但也意味着这个账号可以在所有表空间无限写数据安全隐患很大。更稳妥的做法是按需分配配额。如果确实需要“所有表空间都不受限”才考虑直接授权GRANT UNLIMITED TABLESPACE TO app_user;但更推荐的是明确指定配额ALTER USER app_user QUOTA 100M ON users; ALTER USER app_user QUOTA 50M ON tbs_app;这里要特别提醒几个误区给用户授予 CREATE TABLE 权限后别忘了检查默认表空间上的配额修改配额后要让应用建立新的数据库连接再测试因为已有会话可能保留了旧的权限快照撤销 UNLIMITED TABLESPACE 之后已经创建的对象不会立刻消失但后续再扩展空间就会受限所以收权限时要评估存量对象的影响。4.2 一个可以直接抄的授权检查脚本我平时会把这些查询整理成一个匿名块接到问题后一条命令跑完省得反复切窗口手敲。在 SQL*Plus 或者 SQLcl 里可以直接用SET SERVEROUTPUT ON DECLARE v_username VARCHAR2(30) : UPPER(USERNAME); BEGIN dbms_output.put_line( 用户基本信息 ); FOR r IN ( SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username v_username ) LOOP dbms_output.put_line(r.username || 默认表空间 || r.default_tablespace || 临时表空间 || r.temporary_tablespace); END LOOP; dbms_output.put_line( 表空间配额 ); FOR r IN ( SELECT tablespace_name, max_bytes FROM dba_ts_quotas WHERE username v_username ) LOOP dbms_output.put_line(r.tablespace_name || max_bytes || r.max_bytes); END LOOP; dbms_output.put_line( 直接系统权限 ); FOR r IN ( SELECT privilege FROM dba_sys_privs WHERE grantee v_username ) LOOP dbms_output.put_line(r.privilege); END LOOP; dbms_output.put_line( 通过角色继承的系统权限 ); FOR r IN ( SELECT rp.granted_role, sp.privilege FROM dba_role_privs rp LEFT JOIN role_sys_privs sp ON sp.role rp.granted_role WHERE rp.grantee v_username AND sp.privilege UNLIMITED TABLESPACE ) LOOP dbms_output.put_line(r.granted_role || - || r.privilege); END LOOP; END; /这个脚本把用户、配额、直接权限、角色继承四个方面都覆盖到了遇到 ORA-01950 先跑一遍基本能把问题限定在一个非常小的范围内。4.3 上线规范如何给应用账号分配表空间与其每次出问题再救火不如在账号创建阶段就把规则定好。我建议新应用账号的创建模板至少包含这几项CREATE USER app_user IDENTIFIED BY 复杂密码 DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp QUOTA 100M ON users QUOTA 0 ON system; GRANT CONNECT, CREATE TABLE TO app_user;这里特意加了一个 QUOTA 0 ON system目的就是防止未来有语句显式把对象往 SYSTEM 上建。别小看这一行很多线上事故都是因为建账号的时候省了这句话后来应用脚本里出现 TABLESPACE system系统表空间被业务对象塞满数据库直接挂掉。另外上线前要有一个固定的核对项应用所需要用到的每个表空间都必须确认账号有配额或者明确使用 UNLIMITED TABLESPACE 直接授权。这个检查项写进发布流程能挡掉相当一部分和 ORA-01950 相关的线上变更。5. 实战问题速查与经验补充5.1 ORA-01950排查速查表下面这个表格是我自己整理的排障时对照着看非常省力场景表现排查方向处理参考默认表空间是 SYSTEM所有普通建表都报 ORA-01950dba_users.default_tablespace改默认表空间或授予 UNLIMITED TABLESPACE无任何表空间配额用户有 CREATE TABLE 但建表失败dba_ts_quotas 为空ALTER USER ... QUOTA或直接授权 UNLIMITED权限来自角色数据字典“有权限”但仍报错dba_role_privs role_sys_privs直接 GRANT UNLIMITED TABLESPACE TO 用户建表语句指定了未授权表空间部分表空间正常部分报错报错信息中的表空间名 dba_ts_quotas加配额或改 TABLESPACE 子句CLOB/BLOB 大对象加了大字段后报错LOB 段所在表空间的配额单独指定 LOB 表空间或加配额调用者权限存储过程程序执行报错手动建表正常AUTHID CURRENT_USER 的属主和调用者配额给调用者加配额或调整权限模型这张表看下来你会发现ORA-01950 的“奇怪”其实并不玄学它只是分布的入口多容易被表面信息带偏方向。5.2 我踩过的一些相关坑和心得有一点我想单独拿出来说ORA-01950 的报错信息里如果没有带表空间名通常说明问题出在默认表空间上如果带了具体的表空间名比如 no privileges on tablespace TBS_APP那问题基本就限定在这个表空间内了。抓住这个细节排障速度能快很多。还有一个容易被忽略的时间节点做数据泵导入impdp时如果目标用户没有目标表空间的配额导入过程也会报 ORA-01950。很多人以为 impdp 是 DBA 操作不会受配额限制但实际上导入的表数据最终要落到用户段里同样会触发配额检查。遇到 impdp 中途报这个错别去翻什么字符集问题先查配额。再多说一句经验修改完配额或权限以后一定要求应用重新连接数据库而不是原地重试。我之前踩过这个坑给用户加完配额后让对方在原会话里直接重跑结果仍然报错折腾了一圈最后才反应过来是会话缓存的问题。换了个新会话之后一切正常。数据库里的权限变更很多情况下是要新会话才完全生效的。关于 ORA-01950我现在的处理习惯已经固化了先看默认表空间再看配额接着看权限来源是不是角色最后才考虑 LOB 段和存储过程权限模型。这套顺序走下来几乎没有遇到定位不了的情况。如果你也被这个报错折腾过不妨按这个思路再梳理一遍手上的环境多半会有豁然开朗的感觉。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →