前阵子有个朋友从 MySQL 转到 Oracle,接手一个小型业务库。他觉得自己在 MySQL 里“建账号、给权限”已经熟得不能再熟,于是照着惯性写了一条 CREATE USER,结果登录直接报 ORA-01045,建表又报 ORA-01950,想查另一套 schema 的表还报 ORA-00942。他把三个报错一起截图发我,配了一句:Oracle 是不是每走一步都要单独开一次权限?
我说,你算是问到点子上了。跟 MySQL 那种“授权一次就基本全通”的模型不同,Oracle 里一个普通用户从出生到能真正干活,中间至少隔着“创建用户、授予会话权限、授予对象权限、处理表空间配额”四道工序。任何一道漏了,应用跑起来都会以各种看不懂的报错来提醒你。这篇文章就是把“Oracle 普通用户创建和授权”这件事从头到尾拆开讲清楚。无论你是刚转 Oracle 的开发,还是要给应用系统建号的运维,照着下面的思路走,能少踩很多坑。
1. 先把 Oracle 的“用户”模型理清楚:为什么这里没有一键授权
1.1 Oracle 的用户和 Schema 是一一对应的
MySQL 里有独立的 database,库下面有表,用户可以被授权访问某个库。Oracle 的逻辑不太一样:每个用户创建后,会自动带一个和用户名完全相同的 schema,这个 schema 是用户默认的对象容器。用户在自己 schema 里建的表、视图、存储过程,天然属于自己,不需要额外授权。
所以 Oracle 里“普通用户”这个概念,本质上不是指权限很小的用户,而是指“没有 DBA 这类管理角色的应用账号或业务账号”。这类用户通常只负责访问自己的对象,或者被授权去访问其他 schema 的对象。
这里有个很容易被忽略的点:Oracle 里的授权单位不是“库”,而是“用户/schema”和“具体对象”。你想让一个新用户读取 scott 用户的 emp 表,不能写“允许访问 scott”,而必须明确写“允许访问 scott.emp”。这种细粒度的设计,刚开始会觉得麻烦,但习惯后会发现权限边界非常清晰。
1.2 12c 之后的容器数据库环境,建用户之前先确认容器位置
如果你用的是 Oracle 12c 之后的版本,那么数据库默认有容器架构 CDB 和 PDB。CDB 是根容器,PDB 是真正的业务数据库。
在这个架构下建普通用户,最容易踩的坑是:用 sys 登录 CDB 后直接执行 CREATE USER,系统提示 ORA-65096: invalid common user or role name。原因很简单,你在 CDB 根容器里只能创建以 C## 开头的公共用户,普通业务用户不应该建在根容器里。
正确做法是先切到业务 PDB,再创建用户。比如:
sql复制ALTER SESSION SET CONTAINER = pdb01;
CREATE USER app_user IDENTIFIED BY "你的密码";
注意:如果你习惯用图形化工具连接,很多工具默认连到的就是某个 PDB,这时反而不用手工切容器。但用命令行操作时,这个步骤很高频,建议把它刻进肌肉记忆。
1.3 别把“普通用户”和“低权限用户”混为一谈
普通用户只是相对于管理员的概念,不代表权限必须少到可怜。一个典型的业务应用账号,可能需要建表、建索引、执行存储过程、访问若干个业务 schema,这些都是普通用户能做的事。
所以真正的问题不是“这个用户普通不普通”,而是“这个账号的业务边界在哪”。我见过不少项目给应用账号直接赋了 DBA 角色,省事是省事,但后果也严重:应用一旦被注入 SQL,攻击者能直接操作整个数据库;日常误操作时也完全没有边界保护。普通用户创建和授权,核心思路永远是围绕业务需求做最小授权,而不是按“好不好用”来做。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 建号实操:从一行 CREATE USER 到账号可登录
2.1 一条可以直接改着用的建号语句
先给一条我日常推荐的建号语句,它比网上很多示例多了 DEFAULT TABLESPACE、TEMPORARY TABLESPACE、PROFILE 和 QUOTA,这几个参数决定了账号以后会不会遇到奇奇怪怪的问题。
sql复制CREATE USER app_rw
IDENTIFIED BY "Init#2025"
DEFAULT TABLESPACE tbs_app_data
TEMPORARY TABLESPACE temp
PROFILE app_profile
QUOTA 500M ON tbs_app_data
QUOTA 0M ON system;
这条语句的意图是:
IDENTIFIED BY指定登录密码,注意 Oracle 12c 之后默认启用了密码大小写敏感,最好用带大小写和特殊字符的强密码,并用双引号包起来避免转义问题。DEFAULT TABLESPACE指定用户默认建表所在的表空间。如果不指定,会落到数据库默认表空间,通常就是 system,没人希望应用表建在 system 里。TEMPORARY TABLESPACE指定排序、临时结果的落盘位置。PROFILE指定密码策略和资源限制,这一步经常被省略,但恰恰能避免 180 天密码过期导致应用半夜断连。QUOTA指定用户在某个表空间可以占用的最大空间。这里我把 system 表空间配额设成 0,相当于设置了一道物理拦截。
2.2 表空间、临时表空间和 Profile 为什么必须一起考虑
先说表空间。Oracle 用户建表时,不一定只使用自己的默认表空间。只要用户被授权了某个表空间的写入配额,它就可以通过 TABLESPACE 子句在这个表空间里建对象。如果 DBA 不指定默认表空间,Oracle 会让他落在数据库默认表空间上,这通常不是我们想要的结果。
临时表空间也一样,如果让用户走了数据库默认临时表空间,大量并发排序时容易挤在一起。给用户指定独立的临时表空间,或者至少确保默认临时表空间空间充足,是规范运维的基本操作。
再说 Profile。举个例子,如果数据库启用了默认密码生命周期(默认 180 天),应用账号到期后就会进入 expired 状态。应用连接池不知道密码过期,不断重试,最后引发的“事故”经常被误判为数据库连接数满了。所以给应用账号单独建一个 Profile,把口令过期设成 UNLIMITED,或者设置一个和密码管理节奏匹配的周期,是很有必要的。
sql复制CREATE PROFILE app_profile LIMIT
FAILED_LOGIN_ATTEMPTS 10
PASSWORD_LIFE_TIME 180
PASSWORD_LOCK_TIME 1;
如果项目安全规范要求定期改密,那就把 180 当作真实的改密周期;如果不要求,就把 PASSWORD_LIFE_TIME 改成 UNLIMITED。
2.3 建号后立刻做状态确认
用户创建完,不要急着授权,先查一下当前状态:
sql复制SELECT username, account_status, default_tablespace, temporary_tablespace, profile
FROM dba_users
WHERE username = 'APP_RW';
account_status 那一列如果是 OPEN,说明账号可以登录。如果是 EXPIRED,说明密码状态不对,需要重新设置;如果是 LOCKED,需要执行 ALTER USER app_rw ACCOUNT UNLOCK;。
我见过有人 CREATE USER 之后没看状态,直接拿应用连接测试,结果看到 ORA-28000 the account is locked,第一时间怀疑创建语句错了。其实只是创建完成后默认的密码策略把账号锁了或置为过期,提前查一下 dba_users 就不会绕这个弯。
3. 授权不是一把梭:系统权限、对象权限和角色怎么分工
3.1 系统权限是“能让用户做什么类型动作”的开关
Oracle 的权限分成两大类:系统权限和对象权限。
系统权限跟具体的表无关,它决定用户能不能执行某种数据库操作。比如:
CREATE SESSION:允许登录数据库,这是最低门槛,没有它一切免谈。CREATE TABLE:允许在自己的 schema 里建表。CREATE VIEW、CREATE SEQUENCE、CREATE PROCEDURE、CREATE TRIGGER:允许创建相应对象。UNLIMITED TABLESPACE:允许无限使用表空间配额,一般不建议轻易给。
把系统权限授予用户的语法是:
sql复制GRANT CREATE SESSION TO app_rw;
GRANT CREATE TABLE TO app_rw;
GRANT CREATE VIEW TO app_rw;
GRANT CREATE SEQUENCE TO app_rw;
提示:在 Oracle 中,系统权限和对象的实际操作权限是分开的。比如你即使有
CREATE TABLE,也只能在自己的 schema 里建表;想操作别人的表,必须有对象权限。
3.2 对象权限是按表、按视图、按存储过程给的访问许可证
对象权限针对的是具体对象,典型的包括 SELECT、INSERT、UPDATE、DELETE、ALTER、EXECUTE 等。
比如让 app_ro 用户访问 txn 用户的订单表:
sql复制GRANT SELECT ON txn.t_order TO app_ro;
GRANT SELECT ON txn.t_order_detail TO app_ro;
这里必须注意两点:
第一,Oracle 没有“一次授权整个 schema 的所有表”的语法。网上有人从 MySQL 过来后会幻想 GRANT SELECT ON txn.* TO app_ro;,这在 Oracle 里是不支持的。你只能一张表一张表授权,或者借助 PL/SQL 批量生成授权语句。
第二,普通用户查询其他 schema 的表时,写法不能只写表名,需要带 schema 前缀 txn.t_order。如果应用希望直接写 t_order,还需要在应用账号下创建同义词:
sql复制CREATE SYNONYM app_ro.t_order FOR txn.t_order;
这会涉及 CREATE SYNONYM 系统权限,如果同义词是全局的,还需要 CREATE PUBLIC SYNONYM。
3.3 CONNECT、RESOURCE、DBA 这些预置角色能不能直接用
很多人习惯直接给新用户 GRANT CONNECT, RESOURCE TO user,这能解决登录和基本建表需求,但严格来说并不精准。
CONNECT角色在 10g 之后基本等价于CREATE SESSION权限,给一个刚创建的普通用户先赋它没错。RESOURCE角色包含CREATE TABLE、CREATE SEQUENCE、CREATE TRIGGER、CREATE PROCEDURE、CREATE TYPE等一堆权限,适合应用账号在自己 schema 里开发。- 但要注意,不同版本下
RESOURCE可能包含UNLIMITED TABLESPACE,这等于悄悄放开了表空间配额限制,和最小权限原则相悖。
所以我的建议是:连接权限可以先给 CONNECT,开发环境图省事可以给 RESOURCE,但生产环境最好按业务对象类型逐个授予系统权限。DBA 角色则无论如何不建议直接给应用账号,除非你明确知道自己在干什么。
3.4 自建角色是批量授权的最佳出口
如果一批账号需要相同的权限,更好的做法不是对每个账号重复授权,而是先建一个角色,把权限授予角色,再把角色授予用户。
比如建一个只读角色:
sql复制CREATE ROLE ro_business;
GRANT SELECT ON txn.t_order TO ro_business;
GRANT SELECT ON txn.t_order_detail TO ro_business;
GRANT ro_business TO app_ro;
这样以后要加一张新表的只读权限,只要给 ro_business 角色加一次,所有继承该角色的查询账号同时生效。权限回收时也只需收回角色。经验之谈:如果你要管理几十个账号,没有角色这层抽象,DBA 的日常工作会变成一场灾难。
4. 表空间配额:最容易漏掉的“隐形权限”
4.1 为什么已经授权 CREATE TABLE,建表还是报 ORA-01950
这是我在论坛里看到频率最高的问题之一。用户能登录,也能执行 CREATE TABLE,但回车后报:
text复制ORA-01950: no privileges on tablespace 'TBS_APP_DATA'
原因在于:Oracle 里“能不能建表”和“能不能在某个表空间里占空间”是两件事。前者由系统权限 CREATE TABLE 控制,后者由表空间配额控制。如果你没有给用户指定表空间配额,即使它拥有 CREATE TABLE,也没有物理空间写入权限。
这个设计我后来觉得挺合理的:权限决定操作资格,配额决定资源占用上限,两者分开管理。如果系统里有 100 个账号都能建表,但各自只能往自己小空间里写,就能避免单个账号把磁盘打满拖垮整个实例。
4.2 三种配额的给法
第一种,给用户在指定表空间里的具体额度:
sql复制ALTER USER app_rw QUOTA 500M ON tbs_app_data;
第二种,给用户所有表空间的无限配额:
sql复制GRANT UNLIMITED TABLESPACE TO app_rw;
第三种,收回某个表空间的写入资格:
sql复制ALTER USER app_rw QUOTA 0 ON system;
我的建议是都从第一种起步。配额大小可以结合业务日增量和表空间剩余空间来估算,先给一个保守值,比如 500M,等业务量上来了再调。不要一开始就给无限制配额,因为以后想改小很容易遇到“用户对象已占用 XX 空间,无法缩到指定值”的问题。
4.3 查配额其实很简单
想知道一个用户在所有表空间的配额和使用情况,直接查:
sql复制SELECT tablespace_name, username, max_bytes, bytes
FROM dba_ts_quotas
WHERE username = 'APP_RW';
max_bytes 为 -1 表示无限配额。如果某张表占空间超大,想确认是不是配额不够,也可以用这个视图排查。顺便提醒一句:ALTER USER app_rw DEFAULT TABLESPACE tbs_app_data; 只是指定默认表空间,并不等于用户拥有了这个表空间的配额。默认表空间和配额这两个设置项经常被混淆,建号时缺一不可。
5. 实战模板:只读查询账号与业务读写账号的完整脚本
5.1 只读查询账号怎么建
常见的场景是 BI 报表系统需要一个只读账号,查询业务库的若干表。这时核心诉求是:能登录、能查指定表、不能改数据、不能建对象。
sql复制-- 切到目标 PDB,按需执行
ALTER SESSION SET CONTAINER = pdb_biz;
CREATE USER bi_query
IDENTIFIED BY "Bi#Query2025"
DEFAULT TABLESPACE tbs_app_data
TEMPORARY TABLESPACE temp
PROFILE app_profile
QUOTA 0M ON tbs_app_data;
GRANT CREATE SESSION TO bi_query;
-- 按表授权,业务新增表时持续补充
GRANT SELECT ON txn.t_order TO bi_query;
GRANT SELECT ON txn.t_order_detail TO bi_query;
GRANT SELECT ON txn.t_customer TO bi_query;
只读账号的 DEFAULT TABLESPACE 其实几乎用不到,因为不需要建表,但最好还是指定一个合理表空间,防止误操作把对象建到默认位置。QUOTA 0M 可以作为保险,即便账号被误赋予了建表权限,也没有空间可写。
5.2 业务读写账号怎么建
应用需要读写自己的表,但不需要访问其他专有 schema,那么标准模板是这样:
sql复制CREATE USER app_rw
IDENTIFIED BY "App#Rw2025"
DEFAULT TABLESPACE tbs_app_data
TEMPORARY TABLESPACE temp
PROFILE app_profile
QUOTA 500M ON tbs_app_data;
GRANT CREATE SESSION TO app_rw;
GRANT CREATE TABLE TO app_rw;
GRANT CREATE VIEW TO app_rw;
GRANT CREATE SEQUENCE TO app_rw;
GRANT CREATE PROCEDURE TO app_rw;
GRANT CREATE TRIGGER TO app_rw;
如果应用需要操作其他 schema 的数据,那么把 txn 下的表通过 GRANT 精准授予:
sql复制GRANT SELECT, INSERT, UPDATE ON txn.t_order TO app_rw;
需要注意权限粒度越细,日常维护成本越高。所以这个时候角色就非常有用。给 app_rw 建一个 rw_business 角色,把跨 schema 权限都加到角色上,后续维护清晰得多。
5.3 脚本化时的几个小习惯
我建议所有建号脚本都保存成带注释的 .sql 文件,放在公司数据库脚本目录里。理由很简单:半年后权限出了问题,你还能翻出当时的脚本,一眼知道当初给过什么。
脚本里每个 GRANT 后面加一段注释是个好习惯,例如:
sql复制-- 2025-03-15: 允许只读账号查询订单主题域
GRANT SELECT ON txn.t_order TO bi_query;
数据库权限管理最怕“人走账清”。代码有版本管理,权限变更也应该留下可追溯的痕迹,哪怕只是在一个文档目录里用日期归档,也比全靠 DBA 的脑子记要可靠。
6. 排错与回收:三个高频报错的完整排查链路
6.1 ORA-01045:登录被拒绝的真正原因是不具备 CREATE SESSION
如果你执行 CREATE USER 后没有做任何授权,拿这个账号一登录,大概率遇到:
text复制ORA-01045: user XXX lacks CREATE SESSION privilege; logon denied
看到这个报错不要慌,它说明用户已经存在,密码也正确,只是没有登录权限。解决办法:
sql复制GRANT CREATE SESSION TO app_rw;
这个错误常常让新手误以为密码错了,导致反复改密。避免这种误判的方式,是记住 Oracle 的登录三步:用户存在、密码正确、拥有 CREATE SESSION。三者缺一不可。
6.2 ORA-01950 / ORA-01536:空间配额不够,不是磁盘满
ORA-01950: no privileges on tablespace 'XXX' 前面说过,是没配额。如果配额给了但不够用,还可能出现 ORA-01536: space quota exceeded for tablespace 'XXX'。
排查脚本:
sql复制SELECT tablespace_name, username, max_bytes, bytes
FROM dba_ts_quotas
WHERE username = 'APP_RW';
对比 max_bytes 和 bytes,如果两者非常接近,那就是配额打满。合理做法是扩大配额,而不是顺手给 UNLIMITED TABLESPACE。如果表空间整体剩余空间不足,那问题就变成“表空间加数据文件”了,是两个不同层面的问题。
6.3 ORA-00942:表或视图不存在,还是真的没权限?
这个报错很狡猾。如果对象确实存在,但你没有访问权限,Oracle 也会提示“table or view does not exist”,而不是“权限不足”。原因是为了防止无权限用户通过报错信息探测对象是否存在。
排查思路分两步。第一步,用授权管理员账号确认对象存在:
sql复制SELECT owner, object_name, object_type
FROM dba_objects
WHERE owner = 'TXN' AND object_name = 'T_ORDER';
第二步,确认当前登录账号是不是被授权:
sql复制SELECT grantee, owner, table_name, privilege
FROM dba_tab_privs
WHERE grantee IN ('BI_QUERY', 'RO_BUSINESS')
AND owner = 'TXN'
AND table_name = 'T_ORDER';
如果没有记录,就需要补授权;如果有授权但应用还报错,再看一下应用连的账号是不是你以为的那个账号,多确认一层没有坏处。
6.4 回收权限时留意 WITH GRANT OPTION 和 WITH ADMIN OPTION
授权时如果带了传递选项,收回时需要格外警惕。
对象权限用 WITH GRANT OPTION,意思是接收者可以把这份权限继续转授给别人。这种授权的连锁关系在 REVOKE 时可能引起级联收回。比如 A 授权给 B 并带上 WITH GRANT OPTION,B 又把权限转授给 C,当 A 回收 B 的权限时,C 的权限也会一并失效。
系统权限用 WITH ADMIN OPTION,它的级联规则不同:被授权的用户如果继续转授,等到源头权限被回收时,后续转授出去的权限不一定会被级联收回。
所以在生产环境授权时,我很少给普通用户带这两个选项。运维视角下,权限的再分发应该由 DBA 统一完成,而不是让权限在业务账号间自由传递。
6.5 权限授予后要不要重启会话
很多人担心改完权限之后应用不生效。Oracle 的权限存储在数据字典中,新会话建立时会重新读取。已经存在的会话如何处理,要分情况:
- 系统权限或对象权限被授予后,新会话会立即生效;已建立的会话需要在权限变更后重新登录才会感知。所以处理权限问题时,可以要求应用重连。
- 如果通过角色授予权限,角色默认可能不是启用状态。可以用
SET ROLE或ALTER USER DEFAULT ROLE来控制会话默认启用的角色。 - 修改用户默认角色后,该用户下次登录才生效。
这些细节在排查“授权了但还报权限不足”时非常关键。很多时候不是授权语句的问题,而是会话还停留在旧权限快照里。
7. 写在最后:几个让我省心很久的运维习惯
7.1 给应用账号建“说明备注”式的命名规范
我习惯把账号用途直接体现在命名里。比如 app_rw 表示读写账号,bi_query 表示报表只读账号,ops_dba 表示运维专用账号。名字取好了,权限审计时一看就知道这个账号是干什么用的,不用再满库翻授权记录。
7.2 定期刷新一遍权限清单
数据库权限不是配好就一劳永逸的,业务变更后旧权限会被遗忘。我每隔一段时间会跑一遍下面的查询,把过期账号和明显过宽的权限找出来:
sql复制SELECT grantee, privilege, admin_option
FROM dba_sys_privs
WHERE grantee NOT IN ('SYS', 'SYSTEM')
AND privilege IN ('UNLIMITED TABLESPACE', 'DROP ANY TABLE', 'DELETE ANY TABLE')
ORDER BY grantee;
看到有应用账号出现 DROP ANY TABLE 这类高危系统权限时,就要去找业务确认是否真的需要。大多数情况下是不需要的。
7.3 改权限前先备份现状
修改用户权限前,先把当前权限快照导出一份:
sql复制SELECT grantee, privilege, admin_option
FROM dba_sys_privs WHERE grantee = 'APP_RW';
万一改错了,照着快照恢复就行。这个动作只要 10 秒,但能避免权限反复调整后“回不到原来状态”的尴尬。
普通用户创建和授权这件事,长期做下来后你会发现,它真正考验的不是 syntax 记没记住,而是你愿不愿意在建号之前多花两分钟想清楚这个账号的边界。把边界想清楚,表空间配额、系统权限、对象权限、角色、Profile 这些模块自然就知道怎么选。等哪天这些报错你不需要搜也能直接定位时,再回来看这篇文章,应该会和我有同样的感受:Oracle 的权限体系,其实一点也不绕。
