ora-16000和error 1290本质相同,均因连接指向只读节点所致;根本原因非存储过程本身,而是隐式编译、definer权限校验或dml调用等需写系统表/元数据的操作在只读库上被禁止。

存储过程调用被路由到只读从库时触发 ORA-16000 / ERROR 1290
根本原因不是存储过程本身有问题,而是执行环境不可写。Oracle 报 ORA-16000: 数据库或可插入数据库是以只读访问方式打开的,MySQL 报 ERROR 1290: The MySQL server is running with the --super-read-only option so it cannot execute this statement,本质一致:底层连接指向了一个不允许写入的节点。
特别注意:即使存储过程里只有 SELECT,只要它内部包含隐式编译(如视图失效后首次查询触发重编译)、或调用了含 DML 的子过程、或自身定义为 SQL SECURITY DEFINER 且 DEFINER 用户在从库不存在,都会在只读库上失败——因为编译、权限校验、DEFINER 验证这些动作本身就需要写系统表或读取可写元数据。
MySQL Router 或读写分离中间件不会解析 CALL 内部逻辑
MySQL Router 默认按语句类型做路由:SELECT 走从库,INSERT/UPDATE/DELETE 走主库。但 CALL 被统一归类为“其他语句”,多数版本默认走读写端口(如 6450),而该端口若配置为自动负载均衡,就可能把 CALL 分发到从库。
- 检查你的 Router 配置中
[routing] section的mode是否为read-write(应设为read-write且绑定明确主库) - 不要依赖
autoReconnect=true:它只负责重连,不改变路由目标,反而可能在重连后落到另一个只读节点 - 若必须用
CALL,显式指定主库连接:Spring 中加@Transactional(propagation = Propagation.REQUIRED, readOnly = false);MyBatis 中确保该方法映射到主数据源
Oracle 从库上视图/包失效导致自动编译失败
Oracle 从库是物理复制,对象状态(如 STATUS = INVALID)会同步,但编译动作需要写 obj$ 等系统表——只读库禁止此操作。典型场景:SELECT * FROM V_TEST_JOIN 时发现视图失效,Oracle 尝试自动编译,立刻抛 ORA-04045 + ORA-16000。
- 修复必须在主库执行:
ALTER VIEW V_TEST_JOIN COMPILE;或SELECT COUNT(*) FROM V_TEST_JOIN(触发主库编译) - 避免在应用层捕获后重试:从库永远无法自行修复失效对象
- 监控建议:定期查
SELECT owner, object_name, status FROM dba_objects WHERE status = 'INVALID' AND owner NOT IN ('SYS','SYSTEM');,在主库批量编译
DEFINER 权限在只读库上验证失败
MySQL 存储过程若定义为 SQL SECURITY DEFINER(默认),执行时会以 DEFINER='user'@'host' 身份校验权限。但如果该用户在从库不存在(例如主库有 'admin'@'localhost',从库没同步该账号),或存在但无对应表权限,就会报 Access denied——这个校验过程需要查询 mysql.user 和 mysql.procs_priv,而只读模式下部分系统表不可读。
- 先确认 DEFINER:
SHOW CREATE PROCEDURE proc_name; - 检查该用户是否存在于从库:
SELECT User, Host FROM mysql.user WHERE User = 'xxx'; - 安全做法:重建过程时改用已知存在于所有节点的账号,如
CREATE DEFINER = 'app_runner'@'%' PROCEDURE ... - 切勿直接改
mysql.proc表:MySQL 5.7+ 禁止写入,云数据库(如 RDS)完全屏蔽
最易被忽略的一点:错误日志里看到 --super-read-only 或 ORA-16000 时,第一反应不该是“改存储过程”,而是立刻确认当前连接的实际后端节点角色。用 SELECT @@read_only, @@super_read_only;(MySQL)或 SELECT DATABASE_ROLE FROM V$DATABASE;(Oracle)直连排查,比翻代码快十倍。










