oracle_19c_ru_ojvm_upgrade.sh一键升级脚本分享

oracle_19c_ru_ojvm_upgrade.sh脚本的初始版本来源于IT邦德的分享,使用原脚本时发现有一些bug,在我的环境中脚本根本跑不通,于是个人在这个脚本的基础上进行了大量的改进与优化,到当前版本可以说算是完全重构了。我用这个脚本进行了大量的测试验证(测试环境和UAT环境Oracle 19c数据库实例打补丁),对于Oracle 19c来说基本上是没有什么问题. 使用此脚本打补丁相当丝滑与惬意。也确实让工作效率飞升,可以腾出很多时间做其他事情。脚本的一些思路与想法,阅读下面代码即可略知一二,如果有不足的地方,也敬请指教。当然,这个脚本暂时没有在其他数据库版本经过测试验证。难免会一些Bug,个人后续也会不断地完善、扩充这个脚本。关于这个脚本的一些基本注意事项,详情请见下面:

注意事项:

  1. 此脚本只在Linux(REHL)平台上测试了Oracle 19c数据库, 虽然经过大量验证,不保证其它平台环境也能运行,可能存在Bug,使用前请进行测试验证,作者不保证脚本没有任何bug

  2. 使用前,根据实际情况修改相关变量.

  3. REQUIRED_OPATCH_VERSION变量需要指定的opatch版本根据官方文档资料指定

  4. 脚本目前还只适用于单实例

  5. Oracle 19c早期版本需要先回滚OJVM补丁,然后安装新的补丁,从Oracle Database 19.17.0开始,官方发布了 RU + OJVM Combination Patch(组合补丁)。在该组合补丁中,OJVM 补丁已被整合进 RU 安装映像,不再需要先回滚旧的 OJVM 补丁. 脚本里面没有考虑早期版本升级需要先回滚OJVM补丁情况. 如有需要,请自行完善.

  6. 多实例环境,又分相同数据库版本和不同数据库版本,这里脚本暂未实现这个功能, 属实太复杂的场景会让脚本变得无比复杂,代码量继续飙增.这个脚本代码行数破千了。如果是相同数据库版本的多实例,只保留一个实例和监听服务,其它关闭,然后跑完脚本,最后在启动其它实例,只需跑datapatch脚本

  7. 脚本的函数prepare_run_sql只是特殊环境需要授权,应该很多人的数据库环境根本不需要这样的授权,可以注释删除这个函数。

#!/bin/bash
#########################################################################################
# #
# Oracle 19c RU + OJVM 一键升级脚本,此脚本初始版本来源于IT邦德,使用原脚本时发现有一些bug#
# 于是个人在其基础上进行了大量的调整与改进,这个脚本基本上可以算是完全重构了。 #
#########################################################################################
# #
# ScriptName : oracle_19c_ru_ojvm_upgrade.sh #
# Author : 潇湘隐者 #
# CerateDate : 2025-08-21 #
# Email : kerry2008code@qq.com #
#***************************************************************************************#
# 变量配置 #
#---------------------------------------------------------------------------------------#
# CONNECT_INFO 连接数据库的方式,默认为系统认证模式(如需适用账号密码访问,调整即可 #
# ORACLE_SID ORACLE_SID #
# ORACLE_HOME ORACLE主目录 #
# PATCH_DIR Oracle的补丁文件存放路径 #
# OPATCH_PATCH Oracle的opatch补丁文件名 #
# REQUIRED_OPATCH_VERSION 要求的最低opatch版本 #
# RU_PATCH RU补丁文件名 #
# OJVM_PATCH OJVM补丁名 #
#---------------------------------------------------------------------------------------#
# 参数说明 #
#---------------------------------------------------------------------------------------#
# 此脚本无须使用参数 #
#---------------------------------------------------------------------------------------#
# Usage: #
# sh oracle_19c_ru_ojvm_upgrade.sh #
# 或 ./oracle_19c_ru_ojvm_upgrade.sh #
#***************************************************************************************#
# 注意事项: #
# 1. 此脚本只在Linux(REHL)平台上测试了Oracle 19c数据库, 虽然经过大量验证,不保证其它 #
# 平台环境也能运行,可能存在Bug,使用前请进行测试验证,作者不保证脚本没有任何bug #
# 2. 使用前,根据实际情况修改相关变量. #
# 3. REQUIRED_OPATCH_VERSION变量需要指定的opatch版本根据官方文档资料指定 #
# 4. 脚本目前还只适用于单实例 #
# 5. Oracle 19c早期版本需要先回滚OJVM补丁,然后安装新的补丁,从Oracle Database 19.17.0 #
# 开始,官方发布了 RU + OJVM Combination Patch(组合补丁)。在该组合补丁中,OJVM 补丁#
# 已被整合进 RU 安装映像,不再需要先回滚旧的 OJVM 补丁. 脚本里面没有考虑早期版本升#
# 级需要先回滚OJVM补丁情况. 如有需要,请自行完善. #
# 6. 多实例环境,又分相同数据库版本和不同数据库版本,这里脚本暂未实现这个功能, #
# 属实太复杂的场景会让脚本变得无比复杂,代码量继续飙增.这个脚本代码行数破千了 #
# 如果是相同数据库版本的多实例,只保留一个实例和监听服务,其它关闭,然后跑完脚本,最后#
# 在启动其它实例,只需跑datapatch脚本 #
#***************************************************************************************#
# Version Modified Date Description #
#***************************************************************************************#
# V.0.0 2025-08-19 IT邦德的原始脚本 #
# V.1.0 2025-08-21 修改/创建此脚本 #
# V.1.1 2025-08-25 增加逻辑判断,fix掉几个bug #
# V.1.2 2025-08-29 关闭/启动数据库实例,关闭/启动监听功能封装成函数#
# 独立出来,方便简单调用 #
# V.1.3 2025-09-01 打补丁前检查各个PDB失效对象信息,打完补丁后执行 #
# 重编译无效对象 #
# V.1.4 2025-09-02 打完补丁,检查补丁安装信息 #
# V.1.5 2025-09-03 完善部分功能与(19c非租户环境)执行检查无 #
# 效对象出错的Bug #
#########################################################################################
# =============== 安全控制 ===============
# 严格错误处理
#set -euo pipefail 
#trap "echo 'ERROR: 脚本异常退出,请检查日志!'; exit 1" ERR
# =============== 配置区(根据实际修改)===============
# ORACLE_SID
export ORACLE_SID="gsp"
# ORACLE_HOME目录
export ORACLE_HOME="/opt/oracle19c/product/19.3.0/db_1"
# 数据库的连接方式,请根据实际情况调整
readonly CONNECT_INFO="conn / as sysdba"
# 数据库补丁存放的路径
readonly PATCH_DIR="/data/soft"
# OPATCH补丁文件名
readonly OPATCH_PATCH="p6880880_190000_Linux-x86-64.zip"
# 要求的最低opatch版本
readonly REQUIRED_OPATCH_VERSION="12.2.0.1.46"
# RU补丁文件名
readonly RU_PATCH="p37960098_190000_Linux-x86-64.zip"
# OJVM补丁文件名
readonly OJVM_PATCH="p37847857_190000_Linux-x86-64.zip"
# 下面变量基本无须修改
ROLLBACK_FILE="${PATCH_DIR}/rollback_${ORACLE_SID}_$(date +%Y%m%d).sql"
LOG_DATE=$(date +%Y%m%d%H%M)
readonly SUCCESS=0
readonly FAILURE=1
LOG_FILE="${PATCH_DIR}/patch_${ORACLE_SID}_${LOG_DATE}.log"
# Log输出方式:log或cmd或all
LOG_OUT_TYPE=all
LSNR_NAME=""
DB_VERSION="19"
IS_MULTI_DB=""
OS_TYPE=""
PDB_LIST=""
# 记录脚本的日志信息输出
log_info(){
 #判断参数个数
 if [ $# -eq 1 ];then
 local log_msg=$1
 case $LOG_OUT_TYPE in
 cmd)
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 ;;
 log)
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 all)
 # log_info暂时不会发送邮件
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 *)
 esac
 elif [ $# -eq 2 ];then
 local log_msg=$1
 case $2 in
 cmd)
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 ;;
 log)
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 all)
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 echo -e "[info]: $(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 *)
 esac
 else
 echo -e "[error]: $(date '+%Y%m%d %H:%M:%S')> the number of parameters is incorrect!"
 fi
}
# 记录脚本的错误信息输出
log_error(){
 #判断参数个数
 if [ $# -eq 1 ];then
 local log_msg=$1
 case $LOG_OUT_TYPE in
 cmd)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 ;;
 log)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 all)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 *)
 esac
 elif [ $# -eq 2 ];then
 local log_msg=$1
 case $2 in
 cmd)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 ;;
 log)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 all)
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}"
 echo -e "[error]:$(date '+%Y%m%d %H:%M:%S')> ${log_msg}" >> "$LOG_FILE"
 ;;
 *)
 esac
 else
 echo -e "[error]: $(date '+%Y%m%d %H:%M:%S')> the number of parameters is incorrect!"
 fi
}
precheck() {
 log_info "precheck开始预检..."
 
 # 1. 检查操作系统
 OS_TYPE=$(uname -a | awk ' { print $1} ')
 log_info "当前操作系统为: "${OS_TYPE}" "
 
 if [ "$OS_TYPE" == "Linux" ];
 then 
 log_info "当前操作系统为 ${OS_TYPE},检查通过"
 else
 log_info "当前脚本没有在Linux之外平台测试过,请谨慎使用!"
 fi 
 
 # 2. 运行脚本的当前用户检查/确认
 if [ "$(whoami)" != "oracle" ]; 
 then
 log_error "必须使用oracle用户执行此脚本!"
 exit ${FAILURE}
 else
 log_info "账号检查正常,当前账号为$(whoami)"
 fi
 
 # 3. Oracle补丁文件检查是否齐全
 if [[ ! -f ${PATCH_DIR}/${RU_PATCH} || ! -f ${PATCH_DIR}/${OJVM_PATCH} || ! -f ${PATCH_DIR}/${OPATCH_PATCH} ]]; 
 then
 log_error "Oracle相关补丁文件缺失!,请检查补丁包文件是否齐全"
 exit ${FAILURE}
 else
 log_info "Oracle安装升级的补丁文件齐全,如下所示:"
 ls -lrt ${PATCH_DIR}/${RU_PATCH}
 ls -lrt ${PATCH_DIR}/${OJVM_PATCH}
 ls -lrt ${PATCH_DIR}/${OPATCH_PATCH}
 fi
 
 # 4. 数据库监听服务检查确认
 local curr_lsn_num
 curr_lsn_num=$(ps -e -o args | grep tnslsnr | grep -v grep |wc -l)
 if [ "${curr_lsn_num}" -eq 0 ];
 then
 log_info "当前环境不存在监听服务或监听服务已经关闭了"
 read -r -p "请输入正确的监听服务名" listener_name
 LSNR_NAME=$(listener_name) 
 elif [ "${curr_lsn_num}" -eq 1 ]
 then
 LSNR_NAME=$(ps -e -o args | grep tnslsnr | grep -v grep | awk '{print $2}' | tr "[:upper:]" "[:lower:]")
 log_info "当前监听名称为: ${LSNR_NAME}"
 elif [ "${curr_lsn_num}" -gt 1 ];
 then
 log_info "当前环境有多个监听服务,请指定监听服务名: "
 read -r -p "请输入正确的监听服务名: " listener_name
 LSNR_NAME=$(listener_name) 
 fi 
 
 # 5. 数据库实例的状态检查确认
 local curr_db_status
 curr_db_status=$(check_db_status)
 
 
 if [ "$curr_db_status" != "OPEN" ]; 
 then
 log_error "数据库已处于关闭状态,数据库必须处于OPEN状态"
 exit ${FAILURE}
 else
 IS_MULTI_DB=$(check_db_multitenant)
 log_info "数据库状态为: ${curr_db_status} "
 fi
 # 5. OPatch版本检查
 log_info "Opatch的版本信息如下所示:"
 $ORACLE_HOME/OPatch/opatch version 
 # 6. 数据库版本信息
 log_info "数据库的版本信息如下所示:"
 sqlplus -S /nolog <<EOF
 whenever sqlerror exit sql.sqlcode
 ${CONNECT_INFO}
 set linesize 120
 select banner_full from v\$version;
 exit;
EOF
 # 7. 检查失效对象信息
 log_info "数据库的失效对象检查."
 check_invalid_obj
 
 # 8. 是否继续补丁升级
 read -r -n1 -p "Do you want to continue installing patches? please choose the [Y/N]?" answer
 case $answer in
 Y | y)
 log_info "precheck预检结束"
 ;;
 N | n)
 log_info "You have chosen to exit the patch installation."
 exit ${FAILURE}
 ;;
 *)
 log_error "your choice is wrong!"
 exit ${FAILURE}
 ;;
 esac
}
check_db_version(){
local curr_db_status
curr_db_status=$(check_db_status)
if [ "${curr_db_status}" == "DOWN" ];
then
 log_error "the oracle instance is down, please check it"
else
 db_version_sql=$(
sqlplus -S /nolog <<EOF |awk -F= "/^a=/ {print \$2}" 
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
${CONNECT_INFO}
SELECT 'a='||REGEXP_SUBSTR(VERSION, '^\d+') FROM V\$INSTANCE;
EXIT
EOF
 )
 if [[ -n "${db_version_sql}" && "${db_version_sql}" -gt 9 ]];
 then
 DB_VERSION=${db_version_sql}
 else
 log_error "获取Oracle数据库版本出错,请检查脚本与日志!"
 fi 
fi
}
# 检查数据库当前状态
check_db_status() {
 local status=""
 local db_inst_num="0"
 db_inst_num=$(ps -ef | grep ora_pmon_${ORACLE_SID} | grep -v grep | wc -l)
 if [ "${db_inst_num}" -eq 0 ]; then
 echo "DOWN"
 else
 status=$(
 sqlplus -S /nolog <<EOF | awk -F= "/^a=/ {print \$2}" 
 set head off pagesize 0 feedback off linesize 400
 whenever sqlerror exit 1
 ${CONNECT_INFO}
 select 'a='||status from v\$instance;
 exit
EOF
 )
 echo "${status}" 
 fi
 
 return "${SUCCESS}"
}
# 检查数据库是否多租户环境
check_db_multitenant() {
 check_db_version
 if [ "${DB_VERSION}" -ge 12 ];
 then 
 IS_PDB_EXIST=$(sqlplus -S /nolog <<EOF
 ${CONNECT_INFO}
 SET HEADING OFF 
 WHENEVER SQLERROR EXIT SQL.SQLCODE
 SELECT COUNT(*) VALUE FROM CDB_PDBS;
 EXIT
EOF
 )
 if [ "$IS_PDB_EXIST" -ge 1 ];
 then
 echo "Y"
 else
 echo "N"
 fi
 else
 echo "N"
 fi
}
# 停止监听服务
stop_listener() {
if [ "$OS_TYPE" == "Linux" ] ; 
then 
 #LSNR_NAME=`ps -ef | grep tns | grep $ORACLE_HOME | grep -v grep | awk '{ print $9 }'`
 LSNR_NAME=$(ps -e -o args | grep tnslsnr | grep -v grep | head -1 | awk '{print $2}' | tr "[:upper:]" "[:lower:]")
elif [ "$OS_TYPE" == "AIX" ] ; 
then
 #LSNR_NAME=`ps -ef | grep tns | grep $ORACLE_HOME | grep -v grep | awk '{ print $10 }'`
 LSNR_NAME=$(ps -ef | grep tnslsnr | grep -v grep | awk '{print $10 }')
elif [ "OS_TYPE" == "HP-UX"] ; 
then
 LSNR_NAME=$(ps -ef | grep tnslsnr | grep -v grep | awk '{print $10 }')
fi
local curr_lsn_num
curr_lsn_num=$(ps -ef | grep tnslsnr | grep -v grep |wc -l)
if [ "$curr_lsn_num" -eq 0 ];
then
 log_info "当前环境数据库监听服务已经停止或不存在, 请检查确认!"
 exit ${FAILURE}
elif [ "$curr_lsn_num" -eq 1 ];
then
 lsnrctl stop "$LSNR_NAME"
elif [ "$curr_lsn_num" -gt 1 ];
then
 read -r -n1 -p "Do you like shutting down all listening services? please choose the [Y/N]?" answer
 case $answer in
 Y | y)
 for lsnr_name_item in ${LSNR_NAME};
 do
 lsnrctl stop "$lsnr_name_item"
 done
 ;;
 N | n)
 read -r -p "Please specify the listener service to be stopped." curr_lsnr_name
 lsnrctl stop "$curr_lsnr_name"
 ;;
 *)
 log_error "your choice was wrong!"
 exit ${FAILURE}
 ;;
 esac
fi
}
# 启动监听服务
start_listener() {
 local curr_lsn_num
 local curr_lsnr_name
 
 if [ -z "${LSNR_NAME}" ]; then
 log_error ""${LSNR_NAME}" is null, please check it"
 read -r -p "Please specify the listener service to be stopped." curr_lsnr_name
 
 lsnrctl start "$curr_lsnr_name"
 
 if [ $? -eq 0 ]; then
 log_info "Oracle监听服务${LSNR_NAME}启动成功"
 else
 log_error "Oracle监听服务${LSNR_NAME}启动失败"
 exit ${FAILURE}
 fi
 else
 
 curr_lsn_num=$(ps -ef | grep tnslsnr | grep "${LSNR_NAME}" | grep -v grep |wc -l)
 if [ "$curr_lsn_num" -eq 0 ];
 then
 lsnrctl start "${LSNR_NAME}"
 if [ $? -eq 0 ]; then
 log_info "Oracle监听服务${LSNR_NAME}启动成功"
 else
 log_error "Oracle监听服务${LSNR_NAME}启动失败"
 exit ${FAILURE}
 fi
 else
 log_info "the listener ${LSNR_NAME} is started now"
 fi
 fi
}
shutdown_oracle() {
 local curr_db_status
 curr_db_status=$(check_db_status)
 # 此处只适用于Linux,HP-UX环境会报错,如需适用于HP-UX的话,其用下面注释代码
 #local curr_lsn_num=$(ps -ef | grep tnslsnr | grep -v grep |wc -l)
 local curr_lsn_num
 curr_lsn_num=$(ps -e -o args | grep tnslsnr | grep -v grep |wc -l)
 if [ "${curr_lsn_num}" -gt 1 ];
 then
 log_info "当前环境中有多个监听,请新开一个窗口进行检查,并手工关闭这些监听服务."
 read -r -n1 -p "数据库监听服务已经关闭了吗,请输入(Y或N)" answer
 case $answer in
 Y | y)
 log_info "数据库监听服务已经关闭!"
 ;;
 N | n)
 log_info "You have chosen to exit the patch installation."
 exit ${FAILURE}
 ;;
 *)
 log_error "your choice is wrong!"
 exit ${FAILURE}
 ;;
 esac
 elif [ "${curr_lsn_num}" -eq 1 ];
 then
 LSNR_NAME=$(ps -e -o args | grep tnslsnr | grep -v grep | awk '{print $2}' | tr "[:upper:]" "[:lower:]")
 log_info "Listener Name: ${LSNR_NAME}"
 lsnrctl stop "${LSNR_NAME}"
 if [ $? -eq 0 ];
 then
 log_info "stop the Listener ${LSNR_NAME} successed."
 else
 log_error "stop the listener ${LSNR_NAME} failed ,please check the log"
 exit ${FAILURE}
 fi
 else
 log_info "the Listener ${LSNR_NAME} is stopped status. it does not need to stop it!"
 fi
 
 
 if [ "${curr_db_status}" == "DOWN" ]; 
 then
 log_info "数据库已处于关闭状态,无需关闭数据库实例!"
 return ${SUCCESS}
 else 
 sqlplus -S /nolog <<EOF
 whenever sqlerror exit sql.sqlcode
 ${CONNECT_INFO}
 shutdown immediate;
 exit;
EOF
 if [ $? -eq 0 ];
 then
 log_info "Oracle instance shutdown done.\n"
 else
 log_error "Oracle instance shutdown failed.\n"
 exit ${FAILURE}
 fi
 INSTANCE_STATUS=$(check_db_status)
 
 if [ "$INSTANCE_STATUS" == "DOWN" ];
 then
 log_info "Oracle instance is shutdown now"
 else
 log_error "the oracle instance status is ${INSTANCE_STATUS}, pleas check it "
 exit $FAILURE
 fi
 fi
}
start_oracle() {
 
 start_listener
 
 curr_db_status=$(check_db_status)
 
 if [ "$curr_db_status" == "DOWN" ]; 
 then
 if [ "${IS_MULTI_DB}" == "Y" ];
 then
 sqlplus -S /nolog <<EOF
whenever sqlerror exit sql.sqlcode
${CONNECT_INFO} 
startup;
alter pluggable database all open;
alter pluggable database all save state; 
exit;
EOF
 if [ $? -eq 0 ]; then
 log_info "Oracle数据库实例启动成功"
 else
 log_error "Oracle数据库实例启动失败"
 exit ${FAILURE}
 fi
 else
 sqlplus -S /nolog <<EOF
whenever sqlerror exit sql.sqlcode
${CONNECT_INFO} 
startup;
exit;
EOF
 if [ $? -eq 0 ]; then
 log_info "Oracle数据库实例${ORACLE_SID}启动成功"
 else
 log_error "Oracle数据库实例${ORACLE_SID}启动失败"
 exit ${FAILURE}
 fi
 fi
 fi
 curr_db_status=$(check_db_status)
 if [ "${curr_db_status}" != "OPEN" ]
 then
 log_error "Oracle数据库实例${ORACLE_SID}启动失败.\n"
 exit ${FAILURE}
 else
 log_info "Oracle数据库实例${ORACLE_SID}启动成功\n" 
 fi
}
# 返回PDB数据库列表
get_pdb_list() {
PDB_LIST=$(sqlplus -S /nolog <<EOF
${CONNECT_INFO}
set pagesize 0 feedback off verify off heading off echo off
select 'CDB\$ROOT' name from dual union all
select name from v\$pdbs where open_mode = 'READ WRITE';
exit;
EOF
)
if [ $? -eq 0 ];
then
 return ${SUCCESS}
else
 return ${FAILURE}
fi
}
# =============== 备份模块 ===============
create_backup() {
 log_info ">>> 创建回滚点..."
 sqlplus -S / as sysdba <<EOF > "$ROLLBACK_FILE"
set serveroutput on
exec dbms_qopatch.get_sqlpatch_status;
exit;
EOF
 log_info ">>> 备份重要文件:"
 $ORACLE_HOME/OPatch/opatch lsinventory -detail > ${PATCH_DIR}/inventory_bak_"$(date +%s)".txt
 #cp ${ORACLE_HOME}/sqlpatch/sqlpatch_bundle/* ${PATCH_DIR}/sqlpatch_bak_$(date +%s)/
}
# 版本比较函数(处理形如x.y.z.a的版本格式)
version_ge() {
 # 比较版本:如果$1 ≥ $2则返回0(成功),否则返回1
 [ "$(printf "%s\n%s" "$2" "$1" | sort -V | head -n1)" = "$2" ]
}
opatch_upgrade() {
local -r patch_file=$1
# 定义OPatch路径
local -r OPATCH_CMD="${ORACLE_HOME}/OPatch/opatch"
 
if [ -f "${PATCH_DIR}/${patch_file}" ];
then
 unzip -o "${PATCH_DIR}/${patch_file}" -d ${PATCH_DIR}
 PATCH_NAME=$(unzip -l "${PATCH_DIR}/${patch_file}" | awk '/\/$/ {print $4}' | cut -d'/' -f1 | sort -u)
 cd "${PATCH_DIR}" || exit
 if [ -d "${ORACLE_HOME}/OPatch" ];
 then
 # 备份旧的OPatch目录
 mv "${ORACLE_HOME}/OPatch" "${ORACLE_HOME}/OPatch.${LOG_DATE}"
 cp -rp "${PATCH_DIR}/${PATCH_NAME}" "${ORACLE_HOME}/OPatch"
 else
 log_error "error, please check it"
 exit ${FAILURE}
 fi
 
 # 检查opatch可执行文件是否存在
 if [ ! -x "${OPATCH_CMD}" ]; then
 log_error "错误:OPatch工具不存在或不可执行,路径:${OPATCH_CMD}"
 exit ${FAILURE}
 fi
 # 获取当前OPatch版本
 log_info "正在检查OPatch版本..."
 CURRENT_OPATCH_VERSION=$("${OPATCH_CMD}" version | awk '/OPatch Version/ {print $3}')
 if [ -z "${CURRENT_OPATCH_VERSION}" ]; then
 log_error "错误:无法获取OPatch版本信息"
 exit ${FAILURE}
 fi
 log_info "当前OPatch版本:${CURRENT_OPATCH_VERSION}"
 log_info "要求的最低OPatch版本:${REQUIRED_OPATCH_VERSION}"
 # 检查版本是否符合要求
 if version_ge "${CURRENT_OPATCH_VERSION}" "${REQUIRED_OPATCH_VERSION}"; then
 log_info "OPatch版本符合要求"
 return $SUCCESS
 else
 log_error "OPatch版本不符合要求,请升级至${REQUIRED_OPATCH_VERSION}或更高版本"
 exit ${FAILURE}
 fi
 "${ORACLE_HOME}"/OPatch/opatch version | grep -q "${OPATCH_VER}" || {
 log_error "错误:OPatch版本不满足要求!"
 exit ${FAILURE}
 }
fi
 
}
# =============== 补丁应用模块 ===============
apply_patch() {
 local patch_file=$1
 local patch_type=$2
 
 log_info "应用${patch_type}补丁:$(basename "${patch_file}")"
 unzip -o "${PATCH_DIR}/${patch_file}" -d "${PATCH_DIR}"
 #PATCH_NAME=$(basename $patch_file .zip)
 PATCH_NAME=$(unzip -l "${PATCH_DIR}/${patch_file}" | awk '/\/$/ {print $4}' | cut -d'/' -f1 | sort -u)
 cd "${PATCH_DIR}/${PATCH_NAME}" || exit
 "${ORACLE_HOME}"/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph .
 "${ORACLE_HOME}"/OPatch/opatch apply -silent 
 if [ $? -eq 0 ];
 then
 log_info "Oracle RU patch apply success"
 else
 log_error "Oracle RU patch apply failed ,please check the log "
 exit ${FAILURE}
 fi
}
data_patch() {
 start_oracle
 $ORACLE_HOME/OPatch/datapatch -verbose
 
 if [ $? -eq 0 ];
 then
 log_info "Oracle PUS patch success"
 else
 log_error "Oracle PUS patch failed ,please check the log "
 exit ${FAILURE}
 fi
 
}
prepare_run_sql(){
local -r sql_cmd_text="GRANT EXECUTE ON HTTPURITYPE TO PUBLIC;"
if [ "$IS_MULTI_DB" == "N" ];
then
 sqlplus -S /nolog <<EOF
 ${CONNECT_INFO}
 whenever sqlerror exit sql.sqlcode
 ${sql_cmd_text}
 exit;
EOF
 if [ $? -eq 0 ];
 then
 log_info "在$pdb_name中执行SQL结束..."
 else
 log_error "在$pdb_name中执行SQL: ${sql_cmd_text} 异常"
 exit ${FAILURE}
 fi
else
 if ! get_pdb_list; then
 exit ${FAILURE}
 fi 
 
 # 检查是否获取到PDB列表
 if [ -z "$PDB_LIST" ]; then
 log_error "错误: 未能获取到PDB列表,请检查数据库连接"
 exit ${FAILURE}
 fi
 
 # 在每个PDB中执行SQL语句
 for pdb_name in ${PDB_LIST};
 do
 if [ -n "$pdb_name" ]; 
 then
 log_info "在$pdb_name中执行SQL开始..."
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 whenever sqlerror exit sql.sqlcode
 ${CONNECT_INFO}
 ALTER SESSION SET CONTAINER="$pdb_name";
 ${sql_cmd_text}
 exit;
EOF
 
 if [ $? -eq 0 ];
 then
 log_info "在$pdb_name中执行SQL结束..."
 else
 log_error "在$pdb_name中执行SQL: ${sql_cmd_text} 异常"
 exit ${FAILURE}
 fi
 
 fi
 done
fi
}
check_invalid_obj() {
local sql_cmd_text=""
if [ "$IS_MULTI_DB" == "Y" ];
then
 
 if ! get_pdb_list; then
 exit ${FAILURE}
 fi 
 
 # 检查是否获取到PDB列表
 if [ -z "$PDB_LIST" ]; then
 log_error "错误: 未能获取到PDB列表,请检查数据库连接"
 exit ${FAILURE}
 fi
 # 在每个PDB中执行SQL语句
 for pdb_name in ${PDB_LIST};
 do
 if [ -n "$pdb_name" ]; 
 then
 sql_cmd_text="
 ${CONNECT_INFO}
 whenever sqlerror exit sql.sqlcode
 set serveroutput on;
 ALTER SESSION SET CONTAINER="$pdb_name";
 set linesize 720
 set pagesize 60
 col object_name for a40
 col object_type for a15
 col owner for a10
 select object_name,object_type,owner,status from dba_objects where status<>'VALID' order by owner,object_name;
 exit;"
 log_info "正在 $pdb_name 中执行SQL..."
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 ${sql_cmd_text}
EOF
 if [ $? -eq 0 ];
 then
 log_info "$pdb_name 中检查无效对象完成."
 else
 log_error "$pdb_name 中检查无效对象异常."
 exit $FAILURE
 fi
 fi
 done
else
 sql_cmd_text="
 ${CONNECT_INFO}
 whenever sqlerror exit sql.sqlcode
 set serveroutput on;
 set linesize 720
 set pagesize 60
 col object_name for a40
 col object_type for a15
 col owner for a10
 select object_name,object_type,owner,status from dba_objects where status<>'VALID' order by owner,object_name;
 exit;"
 log_info "正在 $pdb_name 中执行SQL..."
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 ${sql_cmd_text}
EOF
 if [ $? -eq 0 ];
 then
 log_info "$pdb_name 中检查无效对象完成."
 else
 log_error "$pdb_name 中检查无效对象异常."
 exit $FAILURE
 fi
fi
}
recompile_invalid_obj() {
local sql_cmd_text=""
if [ "$IS_MULTI_DB" == "N" ];
then
 sql_cmd_text="${CONNECT_INFO}
 whenever sqlerror exit sql.sqlcode
 set serveroutput on;
 @?/rdbms/admin/utlrp.sql
 exit;"
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 ${sql_cmd_text}
EOF
 if [ $? -eq 0 ];
 then
 log_info "在$pdb_name 中执行脚本utlrp.sql完成..."
 else
 log_error "在$pdb_name 中执行脚本utlrp.sql出现异常..."
 exit ${FAILURE}
 fi
else
 if ! get_pdb_list; then
 exit ${FAILURE}
 fi 
 
 # 检查是否获取到PDB列表
 if [ -z "$PDB_LIST" ]; then
 log_error "错误: 未能获取到PDB列表,请检查数据库连接"
 exit ${FAILURE}
 fi
 
 # 在每个PDB中执行SQL语句
 for pdb_name in ${PDB_LIST};
 do
 if [ -n "$pdb_name" ]; 
 then
 log_info "正在$pdb_name中执行脚本utlrp.sql开始..."
 sql_cmd_text="${CONNECT_INFO}
 whenever sqlerror exit sql.sqlcode
 set serveroutput on;
 ALTER SESSION SET CONTAINER="$pdb_name";
 @?/rdbms/admin/utlrp.sql
 exit;"
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 ${sql_cmd_text}
EOF
 
 if [ $? -eq 0 ];
 then
 log_info "在$pdb_name 中执行脚本utlrp.sql完成..."
 else
 log_error "在$pdb_name 中执行脚本utlrp.sql出现异常..."
 exit ${FAILURE}
 fi
 
 fi
 done
fi
}
check_patch_info() {
 log_info "执行检查补丁信息如下所示:"
 
 # 执行SQL语句
 sqlplus -S /nolog <<EOF
 whenever sqlerror exit sql.sqlcode
 ${CONNECT_INFO}
 ALTER SESSION SET CONTAINER="$pdb_name";
 set serveroutput on;
 set linesize 640;
 set pagesize 40;
 column action_time for a19
 column action format a16
 column version format a26
 column id for 99
 column comments format a30
 column bundle_series format a10
 select to_char(action_time, 'yyyy-mm-dd hh24:mi:ss') as action_time
 , action
 , version
 , id
 , comments
 , bundle_series 
 from sys.registry\$history
 order by action_time;
 
 set linesize 640;
 set serveroutput on;
 col description for a30; 
 col action_time for a30 ; 
 select patch_id, patch_uid,action, status,action_time,description from dba_registry_sqlpatch;
 
 set linesize 720
 col version_full for a19
 col status for a10;
 col modified for a20;
 select version,version_full,status,modified
 from dba_registry;
 exit;
EOF
 if [ $? -ne 0 ];
 then 
 log_error "执行检查补丁信息的SQL语句出现错误,请检查确认!"
 exit ${FAILURE}
 fi
}
main() {
 #执行预检
 precheck
 # 特殊授权
 prepare_run_sql
 
 #创建备份
 create_backup
 #升级opatch
 opatch_upgrade $OPATCH_PATCH
 #关闭数据库实例
 shutdown_oracle
 #应用RU补丁
 apply_patch $RU_PATCH "RU"
 #应用OJVM补丁
 apply_patch $OJVM_PATCH "OJVM"
 
 #数据库data patch
 data_patch
 # 最终验证
 log_info ">>> 验证补丁状态:"
 $ORACLE_HOME/OPatch/opatch lspatches
 $ORACLE_HOME/OPatch/opatch lsinventory | grep -E "${RU_PATCH%.*}|${OJVM_PATCH%.*}"
 
 log_info ">>>>>> 升级成功!请执行健康检查脚本验证数据库状态 <<<<<<"
 check_invalid_obj
 recompile_invalid_obj
 check_patch_info
 
}
# 执行主函数
main | tee -a "${LOG_FILE}"

微信公众号的文章保存时,部分代码经常会出现部分单词间的空格被"自动删除", 所以建议你从下面的原文或
百度网盘链接获取源代码

作者:潇湘隐者原文地址:https://www.cnblogs.com/kerrycode/p/19117774

%s 个评论

要回复文章请先登录注册