Stories

Detail Return Return

oracle_19c_ru_ojvm_upgrade.sh一鍵升級腳本分享 - Stories Detail

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}"

微信公眾號的文章保存時,部分代碼經常會出現部分單詞間的空格被"自動刪除", 所以建議你從下面的原文或
百度網盤鏈接獲取源代碼

user avatar developer-tianyiyun Avatar
Favorites 1 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.