db_role_change触发器可实现主备库角色切换后服务名自动更新,通过after触发、实时查询v$database角色、启停对应服务并执行alter system register刷新监听注册。
oracle 12c 容灾环境里,主备库角色切换后,应用连接的服务名(tns alias)不会自动更新——它还是指向原来的 primary 名字,但此时那个实例可能已是只读备库。真要让服务名“自动跟着角色走”,靠 tns 配置本身做不到,必须结合数据库内部机制。最常用、最可控的方式,就是用 db_role_change 系统事件触发器,在角色实际变更的瞬间,动态修改监听注册的服务名或重定向行为。
DB_ROLE_CHANGE 触发器能做什么
这个触发器在数据库完成 switchover 或 failover 后、刚进入新角色(PRIMARY 或 STANDBY)时触发一次。它不改变 TNS 名字解析,但可以立刻执行关键动作:
- 调用
DBMS_SERVICE.START_SERVICE/STOP_SERVICE控制服务启停 - 执行
ALTER SYSTEM REGISTER强制监听重新加载服务名 - 写入日志或调用外部脚本通知负载均衡器更新后端地址
- 注意:不能在触发器里做耗时操作(如远程 HTTP 调用),否则会阻塞数据库启动流程
怎么写一个安全可用的 DB_ROLE_CHANGE 触发器
核心是区分角色、只做必要动作,避免循环或冲突。以下是最简可行模板:
CREATE OR REPLACE TRIGGER trig_role_change
AFTER DB_ROLE_CHANGE ON DATABASE
DECLARE
v_role VARCHAR2(30);
BEGIN
SELECT DATABASE_ROLE INTO v_role FROM V$DATABASE;
<p>IF v_role = 'PRIMARY' THEN
DBMS_SERVICE.START_SERVICE('myapp_svc'); -- 启动应用服务名
DBMS_SERVICE.STOP_SERVICE('myapp_std'); -- 停掉备库服务名(如有)
ELSIF v_role = 'PHYSICAL STANDBY' THEN
DBMS_SERVICE.START_SERVICE('myapp_std');
DBMS_SERVICE.STOP_SERVICE('myapp_svc');
END IF;</p><p>-- 强制监听立即刷新注册信息
EXECUTE IMMEDIATE 'ALTER SYSTEM REGISTER';</p><p>EXCEPTION
WHEN OTHERS THEN
NULL; -- 不抛错,避免影响角色切换流程
END;</p>
关键点:
- 必须用
AFTER DB_ROLE_CHANGE,BEFORE不可靠,角色还没真正生效 -
V$DATABASE.DATABASE_ROLE必须在触发器内实时查,不能依赖触发前的状态变量 - 服务名(如
myapp_svc)需提前用DBMS_SERVICE.CREATE_SERVICE创建好,且权限已授给SYS - 触发器必须建在 CDB$ROOT(如果是多租户),否则 PDB 切换时不会触发
监听和 TNS 配置必须配合才有效
光有触发器不够。客户端连接的服务名,最终要靠监听器把请求路由到正确的实例。所以:
- 主备库的
listener.ora中,GLOBAL_DBNAME应设为同一个逻辑名(比如myapp),而不是物理 SID - 每个实例启动后,用
DBMS_SERVICE.START_SERVICE注册的服务名,才是监听实际分发流量的目标 - 客户端 tnsnames.ora 只需配置一个服务名,指向 SCAN 或 VIP,由监听根据当前注册的服务动态路由
- 验证是否生效:
lsnrctl status输出中,看Service "myapp_svc" has 1 instance(s)是否出现在当前主库上
为什么不用 SCAN 或 VIP 自动解决?
SCAN 和 VIP 解决的是“连到哪个节点”,不是“连到哪个角色”。RAC 环境下,VIP 漂移只保证 IP 可达,但如果你没用服务名控制实例启停,备库实例照样会注册服务并接收写请求——结果是 ORA-16000 错误。真正隔离读写流量的,是服务名 + DB_ROLE_CHANGE 的组合。这点容易被忽略,导致切换后应用报错却查不出监听问题。











