最危险的“只读”授权是SELECT ANY TABLE,它赋予用户查询所有非系统表的权限,且无法按schema或表精细回收;正确做法是建角色、授具体对象SELECT权限、再授角色给用户,并禁用隐式写入路径。
直接授 SELECT ANY TABLE 是最危险的“只读”做法
它会让用户能查 scott.emp、hr.departments,甚至你下周新建的 finance.report_2026——只要表不是系统表,全在权限范围内。这不是只读,是裸奔式授权。
问题不止于范围失控:SELECT ANY TABLE 是系统级权限,撤回只能整权收回,没法按 schema 或表名批量清理。更糟的是,如果误加 WITH ADMIN OPTION,被授权者还能把这权限转授出去,形成 DBA 都难追踪的二级授权链。
- 别用
GRANT SELECT ANY TABLE TO user,哪怕只是临时测试 - 别信“先开再收”的说法——权限一旦扩散,回收成本远高于初始设计成本
- 若已有用户被授了该权限,优先执行
REVOKE SELECT ANY TABLE FROM user,再重建最小权限模型
真正可控的只读用户必须基于角色 + 对象权限
核心逻辑:建角色 → 给角色授具体表的 SELECT → 把角色给用户。这样后续增删某张表的只读权限,只需改角色,不影响用户本身。
关键操作分三步:
- 创建角色:
CREATE ROLE app_ro - 批量生成授权语句(在目标 PDB 内执行):
SELECT 'GRANT SELECT ON ' || owner || '.' || table_name || ' TO app_ro;' FROM dba_tables WHERE owner = 'APP_SCHEMA' AND temporary = 'N';
注意过滤temporary = 'N',排除全局临时表;物化视图需额外从dba_mviews查并单独授权 - 授角色给用户:
GRANT app_ro TO readonly_user
别漏掉同义词:如果应用通过同义词访问表,要确保底层对象已授权,且用户有 SELECT 权限才能解析成功。
账户本身必须禁用写入能力,不只是“没授 INSERT”
只控制权限不够,还要封死隐式写入路径。常见疏漏点:
- 建用户时必须设
QUOTA 0 ON users,否则某些工具(如 PL/SQL Developer 导出)可能触发隐式临时段创建 - 用
ALTER USER readonly_user ACCOUNT LOCK锁定账户,避免被误启用;需要时再UNLOCK,比反复REVOKE/GRANT更可控 - 只授
CREATE SESSION,别授CONNECT角色——它在旧版本中可能附带UNLIMITED TABLESPACE等冗余权限 - 验证是否真只读:执行
SELECT * FROM v$session应报ORA-00942;执行CREATE TABLE t AS SELECT * FROM app_schema.orders应报ORA-01031
分区表冷数据隔离不能靠“分区只读”,得用表空间级控制
Oracle 19c 不支持 ALTER TABLE sales MODIFY PARTITION p_2023 READ ONLY,语法直接报 ORA-14048。所谓“分区只读”是伪需求。
生产环境唯一可靠方案是把该分区所在的表空间设为只读:
- 先查分区所在表空间:
SELECT tablespace_name FROM dba_tab_partitions WHERE table_name = 'SALES' AND partition_name = 'P_2023' - 再设表空间只读:
ALTER TABLESPACE ts_sales_2023 READ ONLY - 注意前提:表空间内不能有活动事务,否则命令会卡住或失败;需先
SELECT sid, serial# FROM v$session WHERE status = 'ACTIVE'杀掉相关会话
外部分区(如 ORACLE_HDFS 类型)虽天然只读,但无法与内部分区混用在 UPDATE 场景中——未触发分区裁剪时,整条语句会因访问到外部分区而失败。这点容易被忽略,尤其在升级混合分区表时。


















