alter table ... read only 仅在sql解析阶段阻止新dml,不阻断活跃事务、物化视图刷新、外键级联等内部写入路径,也无效于动态execute immediate;需配合权限回收、vpd或os级只读挂载实现强防护。
alter table ... read only 为什么不能阻止所有 dml
因为 read only 是表级逻辑锁,不阻断底层事务机制,也不影响已开启的活跃事务。它只对新发起的 insert、update、delete、truncate 等语句在解析阶段报错,但对 select for update、某些 ddl(如加注释)、或通过物化视图日志间接修改等行为无约束力。
常见错误现象:ORA-12081: UPDATE command not allowed on table "SCHEMA"."TABLE_NAME" 只出现在执行时,不是建表时;而你可能发现 UPDATE 仍成功——那大概率是事务已开启、语句在 READ ONLY 设置前就进了执行队列。
- 必须在空闲状态下设置:确保没有未提交事务正在访问该表
- 不阻止
SELECT或SELECT ... FOR UPDATE(后者会报错,但 Oracle 12c+ 允许在只读表上执行只读版本的FOR UPDATE NOWAIT) - 不阻止
ANALYZE、COMMENT ON、GRANT等 DDL - 对分区表,
READ ONLY作用于整个表,不能只设某一分区为只读
怎么安全启用 READ ONLY 并验证生效
关键不是“设了就行”,而是确认当前无写入上下文、且后续调用真被拦住。直接 ALTER TABLE t READ ONLY 很容易漏掉隐式事务或应用连接池里的长连接。
- 先查活跃事务:
SELECT * FROM v$transaction WHERE xidusn IN (SELECT used_ublk FROM v$session WHERE sql_id IN (SELECT sql_id FROM v$sql WHERE sql_text LIKE '%t%')); - 再查是否有 session 正在修改该表:
SELECT sid, serial#, sql_id FROM v$session WHERE sql_id IN (SELECT sql_id FROM v$sql WHERE sql_text LIKE 'UPDATE%t%' OR sql_text LIKE 'INSERT%t%'); - 执行后立刻验证:
SELECT read_only FROM dba_tables WHERE table_name = 'T' AND owner = 'SCHEMA';—— 值应为YES - 手动测试写操作:
UPDATE t SET c=1 WHERE ROWNUM=1;必须返回ORA-12081,否则说明没生效或权限绕过(比如用SYS用户操作)
READ ONLY 表和物化视图日志、主键约束的关系
很多人以为设成只读就能高枕无忧,结果发现物化视图刷新或外键级联更新仍能改数据——因为 READ ONLY 不干涉依赖机制的内部写入路径。
- 如果该表是物化视图基表且启用了
ON COMMIT REFRESH,刷新过程会绕过READ ONLY检查(Oracle 内部以系统模式 bypass) - 外键
ON DELETE CASCADE删除父表记录时,子表行被自动删掉,也不会触发ORA-12081 - 主键/唯一约束本身不冲突,但如果你用
DISABLE VALIDATE禁用约束后再设只读,Oracle 允许(但不推荐),此时数据一致性完全靠人肉维护 -
READ ONLY表无法作为UPDATABLE物化视图的主表(会报ORA-12052)
替代方案:比 READ ONLY 更严格的控制手段
当业务真需要“绝对不可写”,仅靠 READ ONLY 不够。它本质是协作式约束,不是强制隔离。
- 回收 DML 权限:
REVOKE INSERT, UPDATE, DELETE ON t FROM public;+ 显式授权给极少数运维角色 - 使用虚拟私有数据库(VPD)策略函数返回
1=0过滤所有写条件(需注意性能开销) - 在应用层加拦截中间件(如 ShardingSphere 的 SQL 审计规则),比数据库层更早掐断
- 对归档表,考虑迁出生产库,用只读文件系统挂载(
mount -o ro),从 OS 层杜绝写入可能
最常被忽略的一点:READ ONLY 不影响 DBMS_SCHEDULER 或存储过程里动态拼写的 EXECUTE IMMEDIATE 写操作——只要执行者有权限,照样能绕过。真要防死,得配合权限收窄+代码审计。










