liuziyi

liuziyi

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;

同时确认:

  1. 应用连接串 / TNS / 服务名已指向新主库;
  2. VIP、监听、防火墙、负载均衡已切换;
  3. 备份任务、监控、OGG/CDC 等下游组件已按新角色调整;
  4. 新备库 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 才能重新加入,不可与原流程混用。

posted on 2026-10-23 11:44  刘子毅  阅读(14)  评论(0)    收藏  举报

导航