Иногда приходится восстанавливать базу данных на какое-то конкретное время, делать это вручную весьма рутинно и скучно, поэтому решил таки автоматизировать и написать скриптик. Сразу скажу что rman'ом не пользуюсь, т.к. ну не dba я, потому просто и не довелось его освоить (буду исправляться! :)), на все нет времени, много других задач. Ежедневно делаю холодные копии и пакую архивлоги, обычно хватает холодных копий, но иногда все же требуется восстанавливать на конкретное время, поэтому восстанавливаю требуемую холодную копию/архивлоги/контроллы и используя sqlplus накатываю архивлоги. Скрипт аналогично сам находит требуемые копии, распаковывает, накатывает архивлоги, теперь вместо хх часов, буду тратить порядка 2 минут на запуск скрипта с нужными параметрами, ну если все резервные копии есть и хранятся в необходимом порядке, иначе скрипт выдаст ошибку и придется продолжить вручную...
Скрипт написан на bash. Писался и отлаживался на онлайн платформе onlinegdb.com. Для проверки скрипта была создана структура папок и файлов как на рабочем сервере (см. комментарии в скрипте), так же написаны функции-заглушки для sqlplus и sendmail. Потом слегка доделал скрипт после пары запусков на живом сервере.
PS: скрипт все же немного сырой, он не сработает если нужно восстановить в течении текущего дня, надо будет это исправить и скорее всего как-то надо его декомпозировать и сделать возможность пропуска каких-то этапов.
Ссылка на скрипт на onlinegdb.com
#!/bin/bash | |
################################################## | |
# recover-db-untiltime v1.3 # | |
# # | |
# Скрипт восстановления инстанса Oracle на дату # | |
# сохраняет текущее состояние БД, # | |
# находит холодный бекап и разворачивает его, # | |
# находит и разворачивает архивлоги, # | |
# накатывает архивлоги до требуемой даты. # | |
# автор: Захаров Сергей # | |
# email: s.a.zakharov@gmail.com # | |
# https://developer-core.blogspot.com/ # | |
################################################## | |
################################################## | |
# Настройки скрипта: # | |
# # | |
# имя инстанса | |
ORACLE_SID='KTSR19' | |
# дата на которую требуется восстановить инстанс | |
RECOVERY_DATE='2022-05-23 15:37:00' | |
EMAIL='admin@email.com' | |
SENDMAIL='/usr/sbin/sendmail' | |
# путь к инстансу БД | |
DB_PATH='/oradata/KTSR' | |
# путь резервного копирования базы данных | |
DB_BCK_PATH="$DB_PATH/backup" | |
# путь резервного копирования текущей базы данных | |
CUR_DB_BCK_PATH='/vol/data1/db-backups/current' | |
LOG_FILE="$DB_BCK_PATH/recovery.log" | |
# # | |
################################################## | |
################################################## | |
# подготовка тестового окружения для скрипта # | |
# удалить из рабочего скрипта!! # | |
rootfs='/dev/shm' | |
DB_BCK_PATH="${rootfs}${DB_BCK_PATH}" | |
DB_PATH="${rootfs}${DB_PATH}" | |
CUR_DB_BCK_PATH="${rootfs}${CUR_DB_BCK_PATH}" | |
LOG_FILE="${rootfs}${LOG_FILE}" | |
mkdir -p $DB_PATH | |
mkdir -p $DB_PATH/archive | |
touch $DB_PATH/archive/1_00001.dbf | |
mkdir -p $DB_PATH/ctl | |
touch $DB_PATH/ctl/control01.ctl | |
mkdir -p $DB_PATH/data | |
touch $DB_PATH/data/data.dbf | |
mkdir -p $DB_PATH/idx | |
touch $DB_PATH/idx/index.dbf | |
mkdir -p $DB_PATH/redo | |
touch $DB_PATH/redo/redo.dbf | |
mkdir -p $DB_PATH/undo | |
touch $DB_PATH/undo/undo.dbf | |
mkdir -p $DB_PATH/temp | |
touch $DB_PATH/temp/undo.dbf | |
mkdir -p $DB_PATH/replicate | |
mkdir -p $CUR_DB_BCK_PATH | |
mkdir -p $DB_BCK_PATH/202205210357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205220357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205230357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205240357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205250357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205260357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/202205270357.KTSR19.cold | |
mkdir -p $DB_BCK_PATH/KTSR19.cold | |
mkdir -p $DB_BCK_PATH/some_dir | |
touch $DB_BCK_PATH/ktsr19.base.[20200811].before_pay_st_close.tar.gz | |
tar -cv --exclude=$DB_PATH/backup* $DB_PATH 2>/dev/null | bzip2 -c1 > $DB_BCK_PATH/202205230357.KTSR19.cold/202205230357.KTSR19.base.tar.bz2 | |
tar -cv $DB_PATH/archive 2>/dev/null | bzip2 -c1 > $DB_BCK_PATH/202205240357.KTSR19.cold/202205240357.KTSR19.archive.tar.bz2 | |
## просто заглушки, чтобы скрипт работал | |
sqlplus(){ | |
echo "sqlplus running with" | |
echo "(arguments): $@" | |
echo "(stdin):" | |
STDIN=`cat /dev/stdin` | |
echo "$STDIN" | |
} | |
sendmail(){ | |
echo "sendmail running with" | |
echo "(arguments): $@" | |
echo "(stdin):" | |
STDIN=`cat /dev/stdin` | |
echo "$STDIN" | |
} | |
SENDMAIL='sendmail' | |
# # | |
################################################## | |
################################################## | |
exit_on_error(){ | |
echo >> $LOG_FILE | |
echo "$1" >> $LOG_FILE | |
send_message | |
exit 1 | |
} | |
send_message(){ | |
dt_finish_script=$( date +%s ) | |
echo >> $LOG_FILE | |
echo '------------------------------------------------------------' >> $LOG_FILE | |
echo "Script execution time: $(( ($dt_finish_script - $dt_start_script) /60 )) minutes" >> $LOG_FILE | |
$SENDMAIL -O DefaultCharSet=koi8-r -FServer_$ORACLE_SID $EMAIL <<END | |
To: $EMAIL | |
Subject: $ORACLE_SID - recovering database to $RECOVERY_DATE | |
X-Priority: 1 | |
X-MSMail-Priority: High | |
$ORACLE_SID - recovering database to $RECOVERY_DATE complete | |
============================================================ | |
$( cat $LOG_FILE ) | |
============================================================ | |
. | |
END | |
} | |
dt_start_script=$( date +%s ) | |
################################################## | |
echo "Start at $( date +'%Y-%m-%d %H-%M' )" ... > $LOG_FILE | |
echo "Restoring $ORACLE_SID to $RECOVERY_DATE" >> $LOG_FILE | |
## Определяем даты использования резервных копий | |
echo >> $LOG_FILE | |
echo "STEP 1." >> $LOG_FILE | |
echo "Determine the dates of the backups used" >> $LOG_FILE | |
dt_recovery=$( date +%Y%m%d%H%M -d "${RECOVERY_DATE}" ) | |
## Получаем список резервных копий, выделяем даты, заносим в массив | |
read -a arr_dt_dirs << EOF | |
$( ls -1 $DB_BCK_PATH/ | grep .${ORACLE_SID}.cold | sed -e ':a;N;$!ba;s/[\n]/ /g;s/\([0-9]\{12\}\)[^ ]\+/\1/g' ) | |
EOF | |
dt_first_idx='' | |
dt_last_idx='' | |
## длина списка директорий | |
len=${#arr_dt_dirs[@]} | |
## определяем ближайших холодный бекап | |
for ((i=0; i < $len; i++)) | |
do | |
if (( ${arr_dt_dirs[$i]} > dt_recovery )); then | |
break | |
fi | |
dt_first_idx=$i | |
if [ -f "$DB_BCK_PATH/${arr_dt_dirs[$i]}.${ORACLE_SID}.cold/${arr_dt_dirs[$i]}.${ORACLE_SID}.base.tar.bz2" ]; then | |
dt_first_idx=$i | |
fi | |
done | |
## выходим из скрипта если не найден | |
if [[ -z "$dt_first_idx" ]]; then | |
exit_on_error "--> Error: nearest cold backup not found" | |
fi | |
## определяем последующий бекап с архивлогами | |
for ((i=$len-1; i > -1; i--)) | |
do | |
if (( ${arr_dt_dirs[$i]} < dt_recovery )); then | |
break | |
fi | |
if [ -f "$DB_BCK_PATH/${arr_dt_dirs[$i]}.${ORACLE_SID}.cold/${arr_dt_dirs[$i]}.${ORACLE_SID}.archive.tar.bz2" ]; then | |
dt_last_idx=$i | |
fi | |
done | |
## выходим из скрипта если не найден | |
if [[ -z "$dt_last_idx" ]]; then | |
exit_on_error "--> Error: archivelogs not found" | |
fi | |
echo "backups found:" >> $LOG_FILE | |
echo "first - nearest cold backup: ${arr_dt_dirs[$dt_first_idx]}" >> $LOG_FILE | |
echo "last - with archivelogs: ${arr_dt_dirs[$dt_last_idx]}" >> $LOG_FILE | |
################################################## | |
## копируем текущее состояние базы | |
echo >> $LOG_FILE | |
echo "STEP 2." >> $LOG_FILE | |
echo "Copying current DB state..." >> $LOG_FILE | |
## выключаем базу | |
echo >> $LOG_FILE | |
echo "Shutdown DB..." >> $LOG_FILE | |
sqlplus /nolog 2>1 1>> $LOG_FILE <<EOF | |
connect / as sysdba | |
shu immediate | |
exit | |
EOF | |
cd $DB_PATH/ | |
echo >> $LOG_FILE | |
echo "Packing the current DB..." >> $LOG_FILE | |
tar cvh `ls -1X ctl*/* redo/* ; ls -1X data/* idx/* temp/* undo/*` 2>>$LOG_FILE | gzip -c1 > $CUR_DB_BCK_PATH/$ORACLE_SID.current-base.tar.gz | |
if (( $? != 0 )); then | |
exit_on_error | |
fi | |
echo >> $LOG_FILE | |
echo "Packing DB controlfiles and redo..." >> $LOG_FILE | |
tar cvh `ls -1X ctl*/* redo/*` 2>>$LOG_FILE | gzip -c1 > $CUR_DB_BCK_PATH/$ORACLE_SID.current-ctl+redo.tar.gz | |
if (( $? != 0 )); then | |
exit_on_error | |
fi | |
echo >> $LOG_FILE | |
ls -lh $CUR_DB_BCK_PATH/$ORACLE_SID.current-*.tar.gz >> $LOG_FILE | |
################################################## | |
## распаковываем ближайший холодный бекап | |
echo >> $LOG_FILE | |
echo "STEP 3." >> $LOG_FILE | |
echo "Unpack the nearest cold backup..." >> $LOG_FILE | |
# Ближайший холодный бэкап к дате восстановления | |
dt_backup=${arr_dt_dirs[$dt_first_idx]} | |
backup_file_path="$DB_BCK_PATH/$dt_backup.${ORACLE_SID}.cold/$dt_backup.${ORACLE_SID}.base.tar.bz2" | |
echo "The nearest cold backup to the restore date" >> $LOG_FILE | |
echo $backup_file_path >> $LOG_FILE | |
# Распаковываем холодный бекап | |
echo >> $LOG_FILE | |
echo "Unpack backup to the instance" >> $LOG_FILE | |
tar xvjf $backup_file_path -C / 2>1 1>>$LOG_FILE | |
if (( $? != 0 )); then | |
exit_on_error "--> Error while unpacking a cold backup" | |
fi | |
################################################## | |
## распаковываем архивлоги | |
echo >> $LOG_FILE | |
echo "STEP 4." >> $LOG_FILE | |
echo "Unpack archivelogs..." >> $LOG_FILE | |
for dt_backup in ${arr_dt_dirs[@]:$(($dt_first_idx+1)):$(($dt_last_idx-$dt_first_idx))} | |
do | |
backup_file_path="$DB_BCK_PATH/$dt_backup.${ORACLE_SID}.cold/$dt_backup.${ORACLE_SID}.archive.tar.bz2" | |
tar xvjf $backup_file_path -C / 2>1 1>>$LOG_FILE | |
if (( $? != 0 )); then | |
exit_on_error "--> Error while unpacking archivelogs" | |
fi | |
done | |
################################################## | |
## возвращаем обратно контрол файлы и реду логи | |
## с текущей базы данных | |
echo >> $LOG_FILE | |
echo "Return back control files and redu logs from the current database..." >> $LOG_FILE | |
tar xvzf $CUR_DB_BCK_PATH/$ORACLE_SID.current-ctl+redo.tar.gz -C $DB_PATH/ 2>1 1>>$LOG_FILE | |
if (( $? != 0 )); then | |
exit_on_error "--> Error while unpacking last current controlfiles and redo logs" | |
fi | |
cp -vpR $DB_PATH/redo $DB_PATH/redo2 2>1 1>>$LOG_FILE | |
if (( $? != 0 )); then | |
exit_on_error "--> Error while copying redo logs" | |
fi | |
cp -vpR $DB_PATH/redo $DB_PATH/redo3 2>1 1>>$LOG_FILE | |
if (( $? != 0 )); then | |
exit_on_error "--> Error while copying redo logs" | |
fi | |
################################################## | |
## удалим репликационные файлы с даты восстановления по настоящее время | |
## т.к. они будут неактуальны | |
echo >> $LOG_FILE | |
echo "STEP 5." >> $LOG_FILE | |
echo "Delete replication files from the recovery date to the present..." >> $LOG_FILE | |
MMIN=$(( ( $(date +%s ) - $(date +%s -d "$RECOVERY_DATE" ) )/60 )) | |
echo "delte from $MMIN minutes ago" >> $LOG_FILE | |
find $DB_PATH/replicate/ -type f -mmin -$MMIN -exec rm -vrf {} 2>1 1>>$LOG_FILE \; | |
################################################## | |
## накатываем архивлоги до указанной даты | |
echo >> $LOG_FILE | |
echo "STEP 6." >> $LOG_FILE | |
echo "Apply archivelogs until the specified date..." >> $LOG_FILE | |
UNTIL_TIME=${RECOVERY_DATE/ /:} | |
sqlplus /nolog 2>1 1>>$LOG_FILE <<EOF | |
prompt connect / as sysdba | |
connect / as sysdba | |
prompt set autorecovery on; | |
set autorecovery on; | |
prompt startup mount | |
startup mount | |
prompt recover database until time '$UNTIL_TIME'; | |
recover database until time '$UNTIL_TIME'; | |
prompt alter database open resetlogs; | |
alter database open resetlogs; | |
exit | |
EOF | |
################################################## | |
send_message |
Комментариев нет:
Отправить комментарий