Oracle ADG 切换步骤与一键切换脚本
一、先明确:switchover 与 failover 的区别
| 类型 | 触发场景 | 数据影响 | 原主库后续角色 |
|---|---|---|---|
| Switchover(计划内切换) | 硬件扩容、机房迁移、版本升级、演练 | 不丢数据,主备 SCN 对齐后角色互换 | 自动降级为新备库,继续接收日志 |
| Failover(故障转移) | 主库宕机、存储损坏、站点灾难 | 可能丢失未传输日志(取决于保护模式) | 原主库需重建或 Flushback 后才能回归 |
你问的"切换"通常指 switchover。下文以 switchover 为主线,文末说明 failover 的一键做法。
Switchover 的底层动作:主库停止接收新事务 → 把最后一点 Redo 传到备库并应用完毕(End-of-Redo)→ 主库降级为备库 → 备库提升为主库 → 角色反转后新备库继续应用。
二、切换前必须确认的 8 项(缺一不可)
| # | 检查项 | 关键视图 / 命令 | 合格标准 |
|---|---|---|---|
| 1 | 归档模式 | ARCHIVE LOG LIST |
Archive Mode |
| 2 | 当前角色 | V$DATABASE.DATABASE_ROLE |
主库 = PRIMARY,备库 = PHYSICAL STANDBY |
| 3 | 日志同步无 Gap | V$ARCHIVE_GAP、V$ARCHIVE_DEST_STATUS.GAP_STATUS |
无行返回;GAP_STATUS = NO GAP |
| 4 | 备库日志应用正常 | V$MANAGED_STANDBY |
MRP0 状态为 APPLYING_LOG / WAIT_FOR_LOG |
| 5 | 切换就绪状态 | V$DATABASE.SWITCHOVER_STATUS |
主库 TO STANDBY/SESSIONS ACTIVE;备库 TO PRIMARY/SESSIONS ACTIVE |
| 6 | 无坏块/无 NOLOGGING 块 | V$DATABASE_BLOCK_CORRUPTION、V$NONLOGGED_BLOCK |
主备库均为 0 行 |
| 7 | 参数与路径一致 | V$PARAMETER、DB_FILE_NAME_CONVERT |
主备 DB_BLOCK_SIZE、COMPATIBLE、字符集一致;转换参数正确 |
| 8 | 回退手段 | RMAN 全备 + 参数文件比对 | 已完成,能回退 |
额外提醒(来自实际案例):
- 主库
V$THREAD中存在CLOSED且未禁用的线程(RAC 节点增减遗留)时,DG Broker 会误报 Gap、Ready for Switchover: No,实际并无日志差异。处理:ALTER DATABASE DISABLE THREAD n;后重新校验。 - 切换前应停止应用写入 / 切断客户端访问,避免切换窗口内产生新数据。
- 切换完成后先确认新主备同步正常,再漂移 VIP;VIP 漂移后需发送免费 ARP,否则可能出现 2~3 分钟同网段不通。
三、方式一:DGMGRL 一键切换(推荐,已配置 Broker)
# 1. 查看配置与校验(Ready for Switchover 必须为 Yes)
dgmgrl -silent sys/password@primdb "show configuration"
dgmgrl -silent sys/password@primdb "validate database verbose stdb"
# 2. 一键切换
dgmgrl -silent sys/password@primdb "switchover to stdb"
# 3. 验证新角色
dgmgrl -silent sys/password@stdb "show configuration"
dgmgrl -silent sys/password@stdb "show database verbose stdb"
要点:
switchover to <备库 db_unique_name>会自动完成主库降级、备库提升、角色反转,无需人工干预。- 使用
dgmgrl /本地连接时,若报ORA-12514(监听不认识服务名),改用dgmgrl sys/pwd@dbname即可。 - 若输出
Ready for Switchover: No,先看Gap Status,再排查虚假 Gap(CLOSED 线程)。
四、方式二:SQL*Plus 手工切换(未配置 Broker)
-- ===== 在【原主库】执行 =====
SELECT database_role, switchover_status FROM v$database; -- 期望 TO STANDBY / SESSIONS ACTIVE
ALTER DATABASE COMMIT TO SWITCHOVER TO PHYSICAL STANDBY WITH SESSION SHUTDOWN;
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
-- ===== 在【原备库】执行 =====
SELECT database_role, switchover_status FROM v$database; -- 期望 TO PRIMARY / SESSIONS ACTIVE
ALTER DATABASE COMMIT TO SWITCHOVER TO PRIMARY WITH SESSION SHUTDOWN;
ALTER DATABASE OPEN;
-- ===== 回到【原主库 / 现备库】启动日志应用 =====
ALTER DATABASE OPEN READ ONLY; -- ADG 只读打开
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE
USING CURRENT LOGFILE DISCONNECT FROM SESSION;
12c 及以上 CDB 环境,上述命令在 CDB$ROOT 下执行。
五、一键切换脚本 adg_switchover.sh
脚本特性:
- 支持 broker 与 sqlplus 两种模式
- 内置全量前置检查,任一硬性条件不满足则中止(不误操作)
- 支持
-d只检查不执行(演练)、-y免交互 - 切换后自动做角色/同步/Gap 验证
- 可选
-v自动发送免费 ARP,缓解 VIP 漂移后短时不通 - 全流程日志落盘,便于审计与回退
#!/usr/bin/env bash
###############################################################################
# adg_switchover.sh Oracle ADG / Data Guard 一键 switchover
#
# 用法:
# ./adg_switchover.sh -p "sys/pwd@primdb" -s "sys/pwd@stdb" [-m broker|sqlplus]
# [-n stdb] [-v 192.168.1.100] [-i eth0] [-y] [-d]
#
# 参数:
# -p 主库连接串(需 sysdba) 必填
# -s 备库连接串(需 sysdba) 必填
# -m 模式:broker(默认)/ sqlplus
# -n 备库 db_unique_name(broker 模式可自动获取)
# -v 切换后需漂移的 VIP(可选)
# -i VIP 所在网卡,默认 eth0(配合 -v)
# -y 跳过交互确认
# -d dry-run:只做检查,不执行切换
#
# 说明:
# 1. 脚本需在能同时连通主备库的主机执行(跳板机或主库主机)。
# 2. -v 的 ARP 广播应在“持有 VIP 的新主库主机”上执行;跨主机时请手工执行。
# 3. 切换前请务必完成 RMAN 全备与参数文件比对。
###############################################################################
set -uo pipefail
MODE="broker"; DRY_RUN=0; ASSUME_YES=0
PRIMARY_CONN=""; STANDBY_CONN=""; STANDBY_NAME=""
VIP=""; IFACE="eth0"
LOG_DIR="$HOME/adg_switch_$(date +%Y%m%d_%H%M%S)"
FAIL=0
usage() { sed -n '2,25p' "$0"; exit 1; }
while getopts "p:s:m:n:v:i:ydh" opt; do
case "$opt" in
p) PRIMARY_CONN="$OPTARG" ;;
s) STANDBY_CONN="$OPTARG" ;;
m) MODE="$OPTARG" ;;
n) STANDBY_NAME="$OPTARG" ;;
v) VIP="$OPTARG" ;;
i) IFACE="$OPTARG" ;;
y) ASSUME_YES=1 ;;
d) DRY_RUN=1 ;;
*) usage ;;
esac
done
[ -z "$PRIMARY_CONN" ] || [ -z "$STANDBY_CONN" ] && usage
case "$MODE" in broker|sqlplus) ;; *) echo "模式只能是 broker 或 sqlplus"; exit 1 ;; esac
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/switchover.log"
exec > >(tee -a "$LOG_FILE") 2>&1
c_red(){ printf '\033[31m%s\033[0m\n' "$*"; }
c_grn(){ printf '\033[32m%s\033[0m\n' "$*"; }
c_ylw(){ printf '\033[33m%s\033[0m\n' "$*"; }
step(){ echo; echo "==================== $* ===================="; }
ok(){ c_grn "[ OK ] $*"; }
warn(){ c_ylw "[WARN] $*"; }
err(){ c_red "[FAIL] $*"; FAIL=$((FAIL+1)); }
# ---------- SQL 辅助函数 ----------
sql_val() { # sql_val <conn> <sql>
printf "SET HEADING OFF FEEDBACK OFF PAGESIZE 0 LINESIZE 2000 VERIFY OFF TRIMOUT ON TRIMSPOOL ON\n%s\nEXIT\n" "$2" \
| sqlplus -s "$1" 2>&1 | tr -d '\r' \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | awk 'NF{print; exit}'
}
sql_run() { # sql_run <conn> (SQL 从标准输入传入)
sqlplus -s "$1" 2>&1 | tr -d '\r'
}
# ---------- 0. 参数确认 ----------
step "0. 执行计划确认"
echo "模式 : $MODE"
echo "主库连接 : $PRIMARY_CONN"
echo "备库连接 : $STANDBY_CONN"
echo "备库名称 : ${STANDBY_NAME:-<自动获取>}"
echo "VIP / 网卡 : ${VIP:-<不处理>} / $IFACE"
echo "Dry-Run : $DRY_RUN"
echo "日志目录 : $LOG_DIR"
if [ "$ASSUME_YES" -eq 0 ] && [ "$DRY_RUN" -eq 0 ]; then
read -r -p "确认执行 switchover?输入 YES 继续: " ans
[ "$ans" = "YES" ] || { echo "已取消"; exit 0; }
fi
# ---------- 1. 主库检查 ----------
step "1. 主库角色与环境检查"
P_ROLE=$(sql_val "$PRIMARY_CONN" 'SELECT database_role FROM v$database;')
P_OPEN=$(sql_val "$PRIMARY_CONN" 'SELECT open_mode FROM v$database;')
P_LOG=$(sql_val "$PRIMARY_CONN" 'SELECT log_mode FROM v$database;')
P_SW=$(sql_val "$PRIMARY_CONN" 'SELECT switchover_status FROM v$database;')
P_NAME=$(sql_val "$PRIMARY_CONN" 'SELECT db_unique_name FROM v$database;')
echo "database_role=$P_ROLE open_mode=$P_OPEN log_mode=$P_LOG switchover_status=$P_SW"
[ "$P_ROLE" = "PRIMARY" ] && ok "主库角色正确" || err "连接的不是主库($P_ROLE)"
[ "$P_LOG" = "ARCHIVELOG" ] && ok "归档模式已开启" || err "未开启归档模式"
[ "$P_OPEN" = "READ WRITE" ] && ok "主库为读写状态" || err "主库 open_mode 异常($P_OPEN)"
case "$P_SW" in
"TO STANDBY"|"SESSIONS ACTIVE") ok "主库切换状态正常($P_SW)" ;;
*) err "主库 switchover_status 异常($P_SW),检查归档目的地/日志传输" ;;
esac
# ---------- 2. 备库检查 ----------
step "2. 备库角色与日志应用检查"
S_ROLE=$(sql_val "$STANDBY_CONN" 'SELECT database_role FROM v$database;')
S_SW=$(sql_val "$STANDBY_CONN" 'SELECT switchover_status FROM v$database;')
S_NAME=$(sql_val "$STANDBY_CONN" 'SELECT db_unique_name FROM v$database;')
MRP=$(sql_val "$STANDBY_CONN" "SELECT status FROM v\$managed_standby WHERE process LIKE 'MRP%' AND ROWNUM=1;")
echo "database_role=$S_ROLE switchover_status=$S_SW MRP=$MRP db_unique_name=$S_NAME"
[ "$S_ROLE" = "PHYSICAL STANDBY" ] && ok "备库角色正确" || err "备库角色异常($S_ROLE)"
case "$S_SW" in
"TO PRIMARY"|"SESSIONS ACTIVE") ok "备库切换状态正常($S_SW)" ;;
*) err "备库 switchover_status 异常($S_SW)" ;;
esac
case "$MRP" in
APPLYING_LOG|WAIT_FOR_LOG|"") [ -z "$MRP" ] && warn "未查询到 MRP 进程(可能为实时应用未启动)" || ok "MRP 状态正常($MRP)" ;;
WAIT_FOR_GAP) err "MRP 等待日志 Gap(WAIT_FOR_GAP)" ;;
*) warn "MRP 状态:$MRP" ;;
esac
[ -z "$STANDBY_NAME" ] && STANDBY_NAME="$S_NAME"
echo "本次切换目标备库:$STANDBY_NAME"
# ---------- 3. 日志同步 / Gap 检查 ----------
step "3. 日志同步与 Gap 检查"
GAP_P=$(sql_val "$PRIMARY_CONN" 'SELECT COUNT(*) FROM v$archive_gap;')
GAP_S=$(sql_val "$STANDBY_CONN" 'SELECT COUNT(*) FROM v$archive_gap;')
BAD_DEST=$(sql_val "$PRIMARY_CONN" "SELECT COUNT(*) FROM v\$archive_dest_status WHERE status IN ('ERROR','DEFERRED');")
echo "主库 archive_gap=$GAP_P 备库 archive_gap=$GAP_S 异常归档目的地=$BAD_DEST"
[ "$GAP_P" = "0" ] && ok "主库无归档 Gap" || err "主库存在 $GAP_P 个归档 Gap"
[ "$GAP_S" = "0" ] && ok "备库无归档 Gap" || err "备库存在 $GAP_S 个归档 Gap"
[ "$BAD_DEST" = "0" ] && ok "归档目的地状态正常" || err "存在 $BAD_DEST 个 ERROR/DEFERRED 的归档目的地"
# ---------- 4. 坏块 / 非日志块检查 ----------
step "4. 数据块完整性检查(主备库)"
BC_P=$(sql_val "$PRIMARY_CONN" 'SELECT COUNT(*) FROM v$database_block_corruption;')
BC_S=$(sql_val "$STANDBY_CONN" 'SELECT COUNT(*) FROM v$database_block_corruption;')
NL_P=$(sql_val "$PRIMARY_CONN" 'SELECT COUNT(*) FROM v$nonlogged_block;')
NL_S=$(sql_val "$STANDBY_CONN" 'SELECT COUNT(*) FROM v$nonlogged_block;')
echo "主库坏块=$BC_P 非日志块=$NL_P | 备库坏块=$BC_S 非日志块=$NL_S"
[ "$BC_P" = "0" ] && ok "主库无坏块" || err "主库存在坏块,先处理"
[ "$BC_S" = "0" ] && ok "备库无坏块" || err "备库存在坏块,先处理"
[ "$NL_P" = "0" ] && ok "主库无非日志块" || warn "主库存在 $NL_P 个 NOLOGGING 块"
[ "$NL_S" = "0" ] && ok "备库无非日志块" || warn "备库存在 $NL_S 个 NOLOGGING 块"
# ---------- 5. 虚假 Gap 提示 ----------
step "5. 线程状态检查(排除虚假 Gap)"
CLOSED_TH=$(sql_val "$PRIMARY_CONN" "SELECT COUNT(*) FROM v\$thread WHERE status='CLOSED';")
echo "CLOSED 线程数:$CLOSED_TH"
[ "$CLOSED_TH" = "0" ] && ok "无 CLOSED 线程" \
|| warn "存在 $CLOSED_TH 个 CLOSED 线程,可能导致 Broker 误报 Gap;必要时 ALTER DATABASE DISABLE THREAD n;"
# ---------- 6. Broker 校验 ----------
if [ "$MODE" = "broker" ]; then
step "6. Data Guard Broker 校验"
VALIDATE_OUT=$(dgmgrl -silent "$PRIMARY_CONN" "validate database verbose $STANDBY_NAME" 2>&1)
echo "$VALIDATE_OUT"
if grep -qi "Ready for Switchover:[[:space:]]*Yes" <<<"$VALIDATE_OUT"; then
ok "Broker 校验通过:Ready for Switchover = Yes"
else
err "Broker 校验未通过(Ready for Switchover 非 Yes),禁止切换"
fi
fi
# ---------- 7. 阻断判定 ----------
step "7. 检查结果汇总"
if [ "$FAIL" -gt 0 ]; then
c_red "共 $FAIL 项检查未通过,终止切换。请先处理后再执行。"
exit 2
fi
c_grn "全部前置检查通过。"
if [ "$DRY_RUN" -eq 1 ]; then
c_ylw "Dry-Run 模式,仅检查不执行。"
exit 0
fi
# ---------- 8. 执行切换 ----------
step "8. 执行 switchover"
if [ "$MODE" = "broker" ]; then
echo ">>> dgmgrl switchover to $STANDBY_NAME"
SW_OUT=$(dgmgrl -silent "$PRIMARY_CONN" "switchover to $STANDBY_NAME" 2>&1)
echo "$SW_OUT"
if grep -qi "Switchover succeeded" <<<"$SW_OUT"; then
ok "Broker switchover 成功"
else
err "Broker switchover 失败,请检查输出"
exit 3
fi
else
step "8.1 原主库降级为物理备库"
sql_run "$PRIMARY_CONN" <<'SQL'
WHENEVER SQLERROR CONTINUE
ALTER DATABASE COMMIT TO SWITCHOVER TO PHYSICAL STANDBY WITH SESSION SHUTDOWN;
SQL
step "8.2 原主库以 mount 状态启动"
sql_run "$PRIMARY_CONN" <<'SQL'
WHENEVER SQLERROR CONTINUE
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
SQL
step "8.3 原备库提升为主库并打开"
sql_run "$STANDBY_CONN" <<'SQL'
WHENEVER SQLERROR CONTINUE
ALTER DATABASE COMMIT TO SWITCHOVER TO PRIMARY WITH SESSION SHUTDOWN;
ALTER DATABASE OPEN;
SQL
step "8.4 新备库(原主库)启动日志应用"
sql_run "$PRIMARY_CONN" <<'SQL'
WHENEVER SQLERROR CONTINUE
ALTER DATABASE OPEN READ ONLY;
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;
SQL
fi
sleep 5
# ---------- 9. 切换后验证 ----------
step "9. 切换后验证"
N_ROLE=$(sql_val "$STANDBY_CONN" 'SELECT database_role FROM v$database;')
N_OPEN=$(sql_val "$STANDBY_CONN" 'SELECT open_mode FROM v$database;')
O_ROLE=$(sql_val "$PRIMARY_CONN" 'SELECT database_role FROM v$database;')
O_MRP=$(sql_val "$PRIMARY_CONN" "SELECT status FROM v\$managed_standby WHERE process LIKE 'MRP%' AND ROWNUM=1;")
N_GAP=$(sql_val "$STANDBY_CONN" 'SELECT COUNT(*) FROM v$archive_gap;')
O_GAP=$(sql_val "$PRIMARY_CONN" 'SELECT COUNT(*) FROM v$archive_gap;')
echo "新主库:role=$N_ROLE open_mode=$N_OPEN gap=$N_GAP"
echo "新备库:role=$O_ROLE mrp=$O_MRP gap=$O_GAP"
[ "$N_ROLE" = "PRIMARY" ] && ok "新主库角色正确" || err "新主库角色异常($N_ROLE)"
[ "$N_OPEN" = "READ WRITE" ] && ok "新主库已读写打开" || warn "新主库 open_mode=$N_OPEN"
[ "$O_ROLE" = "PHYSICAL STANDBY" ] && ok "新备库角色正确" || err "新备库角色异常($O_ROLE)"
[ -n "$O_MRP" ] && ok "新备库 MRP 已启动($O_MRP)" || warn "新备库 MRP 未启动"
[ "$N_GAP" = "0" ] && [ "$O_GAP" = "0" ] && ok "双向无归档 Gap" || err "存在归档 Gap,请立即检查"
# ---------- 10. VIP 漂移与免费 ARP ----------
if [ -n "$VIP" ]; then
step "10. VIP 漂移提示"
warn "请确认新主备已同步正常后,再在新主库主机上添加 VIP:ip addr add $VIP/24 dev $IFACE"
if command -v arping >/dev/null 2>&1; then
echo ">>> arping -U -c 3 -I $IFACE -s $VIP"
arping -U -c 3 -I "$IFACE" -s "$VIP" 2>&1 || warn "arping 执行失败(若脚本不在新主库主机上属正常)"
else
warn "未找到 arping,请手工执行:arping -U -c 3 -I $IFACE -s $VIP"
fi
fi
# ---------- 11. 结果 ----------
step "11. 完成"
if [ "$FAIL" -gt 0 ]; then
c_red "切换后仍有 $FAIL 项异常,请按上述提示处理;必要时执行回切。"
exit 4
fi
c_grn "switchover 完成且验证通过。"
echo "日志文件:$LOG_FILE"
echo "回切方式:以本脚本交换 -p / -s 参数后再次执行。"
使用示例
chmod +x adg_switchover.sh
# 1) 演练:只做检查
./adg_switchover.sh -p "sys/Oracle_123@primdb" -s "sys/Oracle_123@stdb" -d
# 2) Broker 一键切换
./adg_switchover.sh -p "sys/Oracle_123@primdb" -s "sys/Oracle_123@stdb" -m broker -y
# 3) 无 Broker,SQL*Plus 流程 + VIP 漂移
./adg_switchover.sh -p "sys/Oracle_123@primdb" -s "sys/Oracle_123@stdb" \
-m sqlplus -v 192.168.1.100 -i eth0 -y
# 4) 回切(参数对调)
./adg_switchover.sh -p "sys/Oracle_123@stdb" -s "sys/Oracle_123@primdb" -m broker -y
六、切换后验证清单
-- 新主库
SELECT database_role, open_mode, protection_mode, switchover_status FROM v$database;
SELECT dest_id, status, gap_status, error FROM v$archive_dest_status WHERE status <> 'INACTIVE';
-- 新备库
SELECT database_role, open_mode, switchover_status FROM v$database;
SELECT process, status, thread#, sequence# FROM v$managed_standby WHERE process LIKE 'MRP%';
SELECT * FROM v$archive_gap;
同时确认:
- 应用连接串 / TNS / 服务名已指向新主库;
- VIP、监听、防火墙、负载均衡已切换;
- 备份任务、监控、OGG/CDC 等下游组件已按新角色调整;
- 新备库
STANDBY_FILE_MANAGEMENT=AUTO,且DB_FILE_NAME_CONVERT/LOG_FILE_NAME_CONVERT正确(否则新主库新增数据文件无法同步到备库)。
七、常见问题(均来自实际案例)
| 现象 | 原因 | 处理 |
|---|---|---|
ORA-12514(DGMGRL 本地连接) |
dgmgrl / 无法解析服务名 |
改用 dgmgrl sys/pwd@dbname |
Ready for Switchover: No + Gap Status: Gap,但查无日志差异 |
主库存在未使用的 CLOSED 线程(RAC 节点增减遗留) |
ALTER DATABASE DISABLE THREAD n; 后重新 validate |
| 切换后 VIP 2~3 分钟不通 | MAC 缓存未刷新、未发免费 ARP | VIP 切换后执行 arping -U -c 3 -I eth0 -s <VIP>;先确认主备同步再漂移 VIP |
| 切换后级联备库 MRP0 异常、新数据文件缺失 | 未用 OMF 且 DB_CREATE_FILE_DEST 为空、CONVERT 参数错误 |
取消 MRP,STANDBY_FILE_MANAGEMENT=MANUAL,ALTER DATABASE CREATE DATAFILE ... AS ...,再改回 AUTO 并启动 MRP |
| OGG 源端 ABENDED | 切换后抽取进程仍指向旧主库 | 迁移抽取到新主库,或按角色切换 SCN 调整抽取 |
主库重启后 ARCn trace 报 ORA-01017 |
Wallet 认证前两种协议失败,密码文件协议成功 | ADG 同步正常时可忽略 |
Failover 说明:主库不可用时,在备库执行 ALTER DATABASE RECOVER MANAGED STANDBY DATABASE FINISH; 再 ALTER DATABASE COMMIT TO SWITCHOVER TO PRIMARY WITH SESSION SHUTDOWN; 并 ALTER DATABASE OPEN;;Broker 环境用 dgmgrl ... "failover to <备库>"。Failover 后原主库必须通过 RMAN 重建或 Flashback 才能重新加入,不可与原流程混用。
浙公网安备 33010602011771号