Евгений Воронянский
Если вы читаете данную статью, то скорее всего Вас настигло несчастье. Экземпляр продуктивной БД доживает последние дни и вот вот перестанет вообще запускаться. Из RMAN-а вы восстановиться не можете, так как пользователи "наработали" достаточно большое количество данных, а архивные журналы с момента аварии безнадежно утеряны. Забегая вперед, отмечу, что с помощью ниже описываемого метода был произведен экпорт-импорт БД размером около 160GB за 18 часов. Платформа:
- Хост с "испорченным" экземпляром - RAM 8G, 4CPU, дисковый массив Clariion, SunOS Solaris, Oracle 8.1.7.4-64
- Хост приемник - RAM 24G, 12CPU, дисковый массив Clariion, SunOS Solaris, Oracle 8.1.7.4-64.
Дополнительное программное обеспечение: ToadForOracle (Quest).
Итак, приступим:
Будем считать что для восстановления мы добавили отдельный диск для расположения на нем исполняемых файлов, а также журналов импорта/экспорта, и для того чтобы не путаться в достаточно большом количестве журналов выполнения создадим структуру дирректорий:
mount /dev/dsk/c1t1d0s0 /mnt/drive
mkdir /mnt/drive/exp-imp
chown oracle:dba /mnt/drive/exp-imp
su - oracle
cd /mnt/drive/exp-imp
mkdir pipe ilog elog sql
Внимание! В данной статье приводится вариант экспорта/импорта от владельца объекта. Т.е. прежде чем стартовать экспорт/импорт, необходимо убедиться что:
- В БД источнике все пользователи имеют право подключаться.ъ
- Нет заблокированных пользователей.
В связи с пунктом 3 необходимо создать список заблокированных пользователе, а также сохранить хэши паролей пользователей.
Для этого используем SQL приведенный ниже:
cd sql
cat > lockuser.sql
Код:
#Список заблокированных пользователей
whenever sqlerror exit sql.sqlcode;
set pause off;
set pages 0;
set linesize 140;
set feedback off;
set termout off;
spool lockuser.spool.sql
select 'alter user "'//username//'" account lock;'
from dba_users
where account_status <> 'OPEN';
exit;
^D
cat >user_password.sql
Код:
#Создание спаска хэшей паролей и
#скрипта подстановки времменного пароля для всех пользователей
whenever sqlerror exit sql.sqlcode;
set pause off;
set pages 0;
set linesize 140;
set feedback off;
set termout off;
column cmd_line format a80
column tick format a3
column sum_bytes format 999,999,999,999
spool return_pass.spool.sql;
select 'alter user "'//username//'" identified by values '''//password//''';' from dba_users;
spool off;
spool change_pass.spool.sql;
select 'alter user "'//username//'" identified by qqq;' from dba_users;
spool off;
exit;
^4D
cat > unlockuser.sql
Код:
#Разблокировка пользователей
whenever sqlerror exit sql.sqlcode;
set pause off;
set pages 0;
set linesize 140;
set feedback off;
set termout off;
spool unlockuser.spool.sql
select 'alter user "'//username//'" account unlock;'
from all_users
/
spool off;
exit;
^D
Ниже приведен скрипт создания пользовательских схем (без данных) в БД приемнике. В нем необходимо исправить SID, домашнюю дирректорию Oracle, а также установить правильные языковые настройки и пароль не схему system, т.е "system/pass@source" и "system/pass@dest" должно являться строкой подключения.
cat > full_export_no_rows.sh
Код:
ORACLE_HOME=/u01/app/oracle/products/8.1.7.4; export ORACLE_HOME
ORACLE_SID=ORCL; export ORACLE_SID
EXP=$ORACLE_HOME/bin/exp
IMP=$ORACLE_HOME/bin/imp
NLS_LANG=AMERICAN_AMERICA.CL8MSWIN1251; export NLS_LANG
DUMP_DIR=/mnt/drive/exp-imp
PIPE=$DUMP_DIR/pipe/exp_full_pipe
ELOG=$DUMP_DIR/elog/$ORACLE_SID-full-`date +%Y%m%d`.elog
ILOG=$DUMP_DIR/ilog/$ORACLE_SID-full-`date +%Y%m%d`.ilog
rm -f $PIPE
mknod $PIPE p # Make the pipe
date >> $ELOG
date >> $ILOG
$EXP system/pass@source FULL=Y ROWS=N CONSTRAINTS=N INDEXES=Y TRIGGERS=N log=$ELOG file=$PIPE& #2>/dev/null 1>/dev/null & # Export to the pipe
$IMP system/pass@dest FULL=Y IGNORE=Y log=$ILOG file=$PIPE #Import from the pipe
date >> $ELOG
date >> $ILOG
rm -f $PIPE
^D
По аналогии создаем скрипт, с помощью которого можно перенести отдельный объект.
cd ..
cat > table_export.sh
Код:
ORACLE_HOME=/u01/app/oracle/products/8.1.7.4; export ORACLE_HOME
ORACLE_SID=ORCL; export ORACLE_SID
EXP=$ORACLE_HOME/bin/exp
IMP=$ORACLE_HOME/bin/imp
NLS_LANG=AMERICAN_AMERICA.CL8MSWIN1251; export NLS_LANG
DUMP_DIR=/mnt/drive/exp-imp
PIPE=$DUMP_DIR/pipe/$1.$2
ELOG=$DUMP_DIR/elog/$ORACLE_SID-$1.$2-`date +%Y%m%d`.elog
ILOG=$DUMP_DIR/ilog/$ORACLE_SID-$1.$2-`date +%Y%m%d`.ilog
rm -f $PIPE
mknod $PIPE p # Make the pipe
date >> $ELOG
date >> $ILOG
$EXP $1/temp_pass@source TABLES=$2 COMPRESS=Y FEEDBACK=1000 log=$ELOG file=$PIPE 2>/dev/null& # Export to the pipe
$IMP $1/temp_pass@dest TABLES=$2 COMMIT=Y IGNORE=Y log=$ILOG file=$PIPE 2>/dev/null # Import from the pipe
date >> $ELOG
date >> $ILOG
rm -f $PIPE
^D
Для полного счастья строим отсортированный по размеру список таблиц, данные которых необходимо перенести с помощью следующего SQL:
cat >list_tbls.sql
Код:
whenever sqlerror exit sql.sqlcode;
set pause off;
set pages 0;
set linesize 150;
set feedback off;
set termout off;
column cmd_line format a80
column tick format a3
column sum_bytes format 999,999,999,999
spool master.list;
--select '/mnt/drive/exp-imp/full_export_no_rows.sh' from dual;
select '/mnt/drive/exp-imp/table_export.sh '//owner//' '//segment_name cmd_line,' # ' tick,
sum(bytes) sum_bytes
from dba_segments
where segment_type = 'TABLE'
and owner <> 'SYS'
and owner not like 'AURORA%'
group by owner, segment_name
order by sum(bytes) desc;
spool off;
exit;
^D
Далее создем скрипт который позволит пускать необходимые процессы в параллели:
cat >paresh
Код:
#!/bin/bash
#Исправте количество запускаемых процессов
#А также путь к командному файлу
message()
{
timestamp=`date +%Y.%m.%d:%H:%M`
echo "$timestamp $*" / tee -a $logfile
return
}
get_shell()
{
echo "`date` $1 Shell Request $$" >> $lklogfile
while : do
next_shell=""
if [ ! -s ${workfile} ]
then
break
fi
if [ ! -f $lockfile ]
then.
echo $$ > $lockfile
echo "`date` $1 Lock Obtained $$" >> $lklogfile
if [ "$$" = "`cat $lockfile`" ]
then
next_shell=`sed -e q $workfile`
sed -e 1d $workfile > ${workfile}.tmp
mv ${workfile}.tmp $workfile
rm -f $lockfile
echo "`date` $1 Shell Issued " >> $lklogfile
return
else
echo "`date` $1 Lock FAULTED $$" >> $lklogfile
fi
fi
sleep 1
done
return
}
paresh_slave()
{
shell_count=0
get_shell $1
while [ "$next_shell" != "" ]
do
shell_count=`expr $shell_count + 1`
message "Slave $1: Running Shell $next_shell"
$next_shell
shell_status=$?
if [ "$shell_status" -gt 0 ]
then
message "Slave $1: ERROR IN Shell $next_shell 2status=$shell_status"
echo "Slave $1: ERROR IN Shell $next_shell status=$shell_status" >> $errfile
fi
get_shell $1
done
message "Slave $1: Done (Executed $shell_count Shells)"
return
}
paresh_driver()
{
rm -f $lklogfile
if [ "$1" = "" ]
then
master_file="/mnt/drive/exp-imp/master.list"
echo $master_file
else
if [ ! -f "$1" ]
then
echo "$0: Unable to find File $1"
exit 1
else
master_file="$1"
fi
fi
if [ "$2" = "" ]
then
parallel_count=24 #(CPU*2)
else
if [ "$2" -lt 1 ]
then
echo "$0: Parallel Process Count Must be > 0"
exit 1
else
parallel_count=$2
fi
fi
message "------------------------------"
message "Master Process ID: $PARESH"
message "Processing File: $master_file"
message "Parallel Count: $parallel_count"
message "Log File: $logfile"
message "------------------------------"
cp $master_file $workfile
while test $parallel_count -gt 0
do
if [ ! -s $workfile ]
then
message "All Work Completed - Stopped Spawning at $parallel_count"
break
fi
$0 $parallel_count &
message "Spawned Slave $parallel_count [pid $!]"
parallel_count=`expr $parallel_count - 1`
done
wait
message "All Done"
return
}
# main
if [ "$PARESH" != "" ];
then
workfile=/tmp/paresh.work.$PARESH
lockfile=/tmp/paresh.lock.$PARESH
lklogfile=/tmp/paresh.lklog.$PARESH
ogfile=/tmp/paresh.log.$PARESH
errfile=/tmp/paresh.err.$PARESH
paresh_slave $*
else
PARESH="$$"; export PARESH
workfile=/tmp/paresh.work.$PARESH
lockfile=/tmp/paresh.lock.$PARESH
lklogfile=/tmp/paresh.lklog.$PARESH
logfile=/tmp/paresh.log.$PARESH
errfile=/tmp/paresh.err.$PARESH
rm -f $errfile
paresh_driver $*
rm -f $workfile
rm -f $lklogfile
if [ -f $errfile ]
then
message "*************************************************"
message "FINAL ERROR SUMMARY. Errors logged in $errfile"
cat $errfile / tee -a $logfile
message "*************************************************"
exit 1
fi
fi
exit
^D
После создания всех необходимых файлов, а также базы данных на целевом хосте со всеми необходимыми табличными пространствами ставим перед фактом руководство, и приступаем к использованию только что созданных файлов:
Построение списков:
cd sql
sqlplus system/pass@source @lockuser.sql
sqlplus system/pass@source @user_password.sql
sqlplus system/pass@source @unlockuser.sql
cd ..
sqlplus system/pass@source @list_tbls.sql
cd sql
Изменение паролей пользователей и снятие блокировок с аккаунтов.
sqlplus system/pass@source @change_pass.spool.sql
sqlplus system/pass@source @unlockuser.spool.sql
Экспорт/импорт пользовательских схем без данных:
cd ..
./full_export_no_rows.sh
Далее внимательно проверяем все что в дирректории ilog, elog и только после окончания экспорта/импорта схем запускаем ./paresh и анализируем лог-файлы процессов импорта/экспорта.
По завершению всех процессов для определения "потерянных" объектов (constraints, index…) используем Toad for Oracle (DBA->Compare Schemas, DBA->Compare Databases), собираем статистику, а также выполняем скрипт для определения разницы в таблицах на уровне количества строк, для работоспособности которого необходимо связать БД линком MIGRATION.WORLD :
cd /mnt/drives/exp-imp/sql
cat > rows_tables_diff.sql
Код:
whenever sqlerror exit sql.sqlcode;
set pause off;
set pages 0;
set linesize 150;
set feedback off;
set termout off;
column cmd_line format a80
column name format a3
column Diff format 999,999,999,999
spool rows_tables_diff.spool.sql;
select 'whenever sqlerror exit sql.sqlcode;'//chr(10)//
'set pause off;'//chr(10)//
'set pages 0;'//chr(10)//
'set linesize 150;'//chr(10)//
'set feedback off;'//chr(10)//
'set termout off;'//chr(10)//
'column cmd_line format a80'//chr(10)//
'column tick format a3'//chr(10)//
'column sum_bytes format 999,999,999,999'//chr(10)//
'spool rows_tables_diff.txt'
from dual;
select
'select name, sum(Dest) - sum(Source) diff '//chr(10)//
'from ( '//chr(10)//
'select '''//table_name//''' name,count(1) Dest, 0 Source '//chr(10)//
'from '//owner//'.'//table_name//'@MIGRATION.WORLD union '//chr(10)//
'select '''//table_name//''' name,0 Dest, count(1) Source '//chr(10)//
'from '// owner //'.'//table_name//' )'//chr(10)//
'group by name;' Str
from all_tables
where owner not in ('SYSTEM','SYS')
and owner not like 'AURORA%'
select chr(10)//'exit;' //chr(10) from dual;
exit;
^D
И в зависомости от того в какой из БД был создан линк выполняем rows_tables_diff.spool.sql. Результат сравнения наблюдаем в rows_tables_diff.txt.
Значительно ускорить экспорт/импорт позволяет установка параметра скрытого параметра oracle - _disable_logging в true на БД в которую производится импорт, но при запуске БД в эксплуатацию его необходимо установить в false.
Удачи.