Работа с Oracle 6: Резервное копирование



Привет! Если ты ещё не устал от моей болтовни, то я осмелюсь рассказать как можно обеспечить резервное копирование базы данных Oracle в своём приложении. Прежде чем начинать проектировать, попробую вкратце объяснить какая задача будет стоять перед нами. Но сначала нам надо понять где Oracle хранит свои данные, и как обеспечить их сохранность.

Я не буду углубляться в теоретические дебри, думаю ты и сам сможешь найти на необъятных просторах рунета литературу по Oracle (ну или закажешь почтой, как я последнее время и делаю). Работать мы будем опять с тестовой схемой SCOTT. Oracle хранит базу данных в файлах DBF в директории "имя_диска-oracle-oradata-имя_бд" у меня соответственно это "H-oracle-oradata-manuf". Вот как выглядит эта директория:

Существует несколько основных видов резервного копирования в Oracle:

1. Холодное копирование файлов данных: подразумевает копирование файлов из указанной выше директории при остановленном сервере Oracle;

2. Горячее копирование файлов данных: подразумевает копирование файлов данных при запущенном сервере;

3. Создание дампа: подразумевает использование утилиты export входящей в состав Oracle;

Вообще, если говорить о сохранности данных в Oracle, существует огромное количество средств его обеспечить, также как существует огромное количество средств для восстановления данных. Но мы рассмотрим случай, когда устройство хранения (винчестер) физически повреждено (сгорел например) и нет возможности восстановить работоспособность базы данных средствами Oracle. Такая ситуация возможна и с так называемыми RAID-массивами, бывали случаи когда сгорал полностью массив 5-го уровня, от этого никто не застрахован. Соответственно необходимо обеспечить резервное копирование файлов БД на несколько устройств хранения, например на резервный сервер, ну или на худой конец на соседний компьютер в локальной сети.

Вот этим мы и займёмся в этот раз, а сохранять будем файлы как раз из указанной директории (папки). Мы рассмотрим только создание холодной копии и использование утилиты export для экспортирования базы в файл дампа.

Итак, при выполнении холодного копирования необходимо остановить работу экземпляра (базы данных), затем скопировать файлы данных в указанное место, затем запустить экземпляр. Восстановление происходит в обратной последовательности. Это минимальные действия предпринимаемые для резервирования базы, так как желательно ещё сохранить следующие файлы INIT.ORA (файл инициализации содержащий параметры запуска экземпляра), SPFILE.ORA (виртуальный файл инициализации экземпляра). Все остальные жизненно необходимые файлы находятся в директории с БД (см. выше).

Выполнение экспорта базы данных подразумевает использование утилиты export (exp.exe). Данная утилита может вызываться из командной строки с кучей различных параметров, что нам и надо. Я укажу лишь основные необходимые нам параметры, с остальными ты можешь ознакомиться в документации к Oracle.

Итак, приступим к проектированию нашего приложения. Выполнение холодного копирования мы будем осуществлять совместно с архиватором WINRAR для сжатия файлов данных, для уменьшения занимаемого ими места. WINRAR я выбрал потому что его также можно вызвать из командной строки указав необходимые параметры.

Для реализации нам необходим сервер Oracle (желательно версии 9), с созданной базой данных и запущенным экземпляром (у меня manuf) и процессом прослушивателя (listener) и среда Delphi (желательно версия 7.0). Запускаем Delphi, создаём новое приложение. Прежде чем мы продолжим я хочу сделать небольшое отступление. В нашем приложении нам надо будет часто запускать внешние процессы, и контролировать их выполнение. Для этой задачи ты можешь воспользоваться функциями WinAPI (WinExec или ShellExecute или CreateProcess), но так как этот процесс более трудоёмкий я решил пойти по пути наименьшего сопротивления и воспользовался компонентами библиотеки RAIZE Components v. 3.0.13. Найти ты их сможешь на сайте ]]>http://www.raize.com]]> или набрав в любом поисковике.

Далее нам необходимо создать вот такую форму:

Ну я думаю здесь объяснять не надо какой компонент как расположить, с этим ты и сам справишься :)

Итак, на первой панели у нас будет отображаться текущее системное время (с помощью Timer), на второй панели мы будем указывать параметры создания холодной копии БД, на третьей параметры экспорта БД, на четвёртой путь копирования архивов по сети. Да кстати, это всё Raize компоненты, правда намного симпатичнее стандартных?

На нижней панели расположены кнопки управления запуском и остановкой таймера, и кнопка выхода из приложения. А теперь всё по порядку. Кидаем на форму Timer, устанавливаем его свойство Enabled в False, на нажатие кнопки Пуск пишем:

Timer1.Enabled:=true;

на нажатие кнопки Стоп пишем:

Timer1.Enabled:=false;

На событие OnTimer для Timer пишем:

resent:= Now;
DecodeTime(Present, Hour, Min, Sec, MSec);
RzEdit3.Text:=IntToStr(Hour); //edit для отображения часов
RzEdit4.Text:=IntToStr(Min); //edit для отображения минут
RzEdit5.Text:=IntToStr(Sec); //edit для отображения секунд

предварительно в разделе private модуля добавив объявление следующих переменных:

Present: TDateTime;
Hour, Min, Sec, MSec: Word;

Можешь скомпилировать проект и запустить, при нажатии кнопки пуск запускается счётчик, при нажатии стоп останавливается:

Теперь добавим на форму RzSelectFolderDialog с закладки Raize Shell, конечно если ты не смог по каким-либо причинам скачать и установить эту библиотеку, то можешь реализовать данную опцию (выбор папки назначения) посредством компонента OpenDialog обрезав имя файла и оставив только путь к папке. Теперь на нажатие кнопки выбора пути к файлам БД надо прописать следующее:

if RzSelectFolderDialog1.Execute then begin
RzEdit1.Text:=RzSelectFolderDialog1.SelectedPathName;
end;

Здесь происходит запись выбранного пути в edit.

Для указания пути для сохранения можно воспользоваться компонентом SaveDialog. Добавляем его на форму, в свойстве FileName пропишем имя сохраняемого файла (у меня backup.rar).

На нажатие кнопки выбора пути для сохранения архива пропишем следующее:

if SaveDialog1.Execute then begin
RzEdit2.Text:=SaveDialog1.FileName;
end;

Здесь также происходит вставка выбранного пути и имени файла в edit.

Далее нам необходимо запрограммировать проверку совпадения указанного времени выполнения архивирования с текущим временем отображаемым на первой панели. Для этого в событии Timer'а OnTimer надо вставить код проверки соответствия значений Edit'ов отображающих текущее время с Edit'ами времени холодного копирования. Изменим процедуру следующим образом:

procedure TForm1.Timer1Timer(Sender: TObject);
 
begin
//установка текущего времени
Present:= Now;
DecodeTime(Present, Hour, Min, Sec, MSec);
 
RzEdit3.Text:=IntToStr(Hour); //edit для отображения часов
RzEdit4.Text:=IntToStr(Min); //edit для отображения минут
RzEdit5.Text:=IntToStr(Sec); //edit для отображения секунд
 
//проверка времени холодного копирования
if (RzEdit3.Text=RzEdit6.Text) and
   (RzEdit4.Text=RzEdit7.Text) and
   (RzEdit5.Text=RzEdit8.Text) then begin
//выполнение
архивирования
end;
end;

Здесь происходит проверка соответствия текущего времени установленному и при совпадении выполняется архивирование холодной копии БД.

Однако прежде чем реализовывать архивирование хочу уточнить один момент. Если мы попытаемся просто запустить Winrar на архивирование в данном потоке, нагрузка будет настолько большой (опять же в зависимости от размера архивируемых данных) что таймер не сможет производить отсчёт времени и зависнет до окончания архивирования. Похожая ситуация встречается во многих программах, например в Total Commander, там при копировании файлов есть возможность запуска процесса копирования в фоновом режиме, соответственно нагрузка с основной программы снимается и мы можем продолжать комфортно работать с ней. Также поступим и мы - создадим отдельный поток в приложении, и уже в нём запустим внешний процесс - архивирование базы данных.

Благо для этого не надо прилагать много усилий - в Delphi всё автоматизировано, несколько нажатий кнопки мышки и всё (почти :) ) готово. Итак, заходим в меню File-New-Other на закладке New выбираем Thread Object. В поле Class Name вводим Arhiv и нажимаем OK.

Затем выполняем следующую последовательность действий: заходим в модуль главной формы (у меня Unit1 по умолчанию), в разделе uses прописываем имя созданного модуля потока (у меня Unit2)

Затем в разделе var главного модуля прописываем следующее:

Здесь мы объявляем новую переменную - наш поток, который при срабатывании таймера будет создаваться и запускаться из данного модуля.

Далее добавляем на главную форму компонент RzLauncher с закладки RzWidgets

и устанавливаем для него следующие свойства:

FileName: Winrar.exe

StartDir: "C:Program FilesWinRAR" (обязательно с кавычками если путь содержит пробелы)

Здесь мы прописали имя выполняемой программы и путь к данной программе, нужно будет только программно установить параметры запуска для Winrar'а. Да, кстати, я указал так сказать статическую ссылку на путь к Winrar'у, правильнее было бы программно его задавать выбором в диалоге, по принципу указания пути для архивирования и пути к базе данных.

Осталось только в модуле потока прописать выполняемые действия и в принципе архивирование почти готово. В единственной процедуре Execute прописываем следующее:

procedure Arhiv.Execute;
begin
  { Place thread code here }
if (Form1.RzEdit1.Text<>'') and
   (Form1.RzEdit2.Text<>'')  then begin
Form1.RzLauncher1.Parameters:='a -inul "'+
Form1.RzEdit2.Text +'" "'+ //файл архива
Form1.RzEdit1.Text + '"';//источник архивирования
Form1.RzLauncher1.Launch;
end;
end;

В данной процедуре происходит проверка наличия указанного пути к базам и пути для сохранения архива, затем задаются параметры запуска Winrar'а, по ним ты можешь получить справку из Winrar'а, единственно отмечу параметр inul - он означает отмену вывода сообщений об ошибках, я сделал это потому что если возникают какие-либо ошибки архивирования появляется окошко с сообщением, и пока ты не нажмёшь ОК процесс не заканчивается, соответственно если окончания этого процесса ожидает какой-нибудь ещё процесс (смотри далее), то дальнейшая работа остановится, так как данный процесс не будет считаться завершённым. Ну и далее соответственно происходит запуск процесса архивирования. По окончании процесса архивирования Winrar закроется и поток будет автоматически уничтожен. Да, и не забудь про кавычки в пути к файлу архива и источнику, у меня они везде добавлены, на всякий случай.

Ещё стоит отметить такой момент, я немного нарушил правила и не стал использовать процедуру Synchronize, это грозит проблемами при использовании нескольких потоков в приложении и попытке потоков обращения к свойствам визуальных компонентов, вот что прописано в модуле потока:

Important: Methods and properties of objects in visual components can only be used in a method called using Synchronize

Программирование архивирования почти закончено, осталось только реализовать в приложении остановку и запуск сервера Oracle. Для этого добавляем на форму ещё два компонента RzLauncher, указав в свойстве FileName первого shutdown.cmd второго: startup.cmd. Теперь давай создадим эти файлы. Открывай блокнот и пиши: oradim -shutdown -sid local, здесь вместо local у тебя должно быть имя твоей БД (экземпляра), которое ты можешь посмотреть в службах. Нажимай сохранить и сохраняй в папке с приложением (обязательно, иначе не найдёт!) под именем shutdown.cmd. Затем изменяй строку на следующее: oradim -startup -sid local, нажимай сохранить как и сохраняй в папке с приложением под именем startup.cmd.

Далее изменяем процедуру OnTimer нашего Timer'а следующим образом:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
//установка текущего времени
Present:= Now;
DecodeTime(Present, Hour, Min, Sec, MSec);
RzEdit3.Text:=IntToStr(Hour); //edit для отображения часов
RzEdit4.Text:=IntToStr(Min); //edit для отображения минут
RzEdit5.Text:=IntToStr(Sec); //edit для отображения секунд
 
//проверка времени холодного копирования
if (RzEdit3.Text=RzEdit6.Text) and
   (RzEdit4.Text=RzEdit7.Text) and
   (RzEdit5.Text=RzEdit8.Text) then begin
//выполнение архивирования
RzLauncher2.Launch;//остановка сервера
end;
end;

Я думаю здесь всё понятно, происходит запуск нашего батника выполняющего остановку сервера. Теперь нам необходимо дождаться остановки сервера (в зависимости от производительности ПК это может занять приличное время), т.к. иначе Winrar не сможет получить монопольный доступ к файлам БД и соответственно они в архив не попадут. Для этого нам нужно всего лишь воткнуть процедуру запуска архивирования в событие OnFinished компонента RzLauncher2, вот как это будет выглядеть:

procedure TForm1.RzLauncher2Finished(Sender: TObject);
begin
Arhiv_thread:=Arhiv.Create(false);
Arhiv_thread.FreeOnTerminate:=true;
end

десь происходит создание нашего потока (Create), свойство FreeOnTerminate=true означает что уничтожение потока будет происходить автоматически по окончании выполнения работы в нём, и нам не прийдётся этого делать вручную. После создания потока управление программы будет передано в процедуру Execute нашего потока (смотри выше).

Таким образом, пока не закончится останов сервера архивирование не будет запущено. Ну и теперь надо аналогичным образом прописать процедуру запуска сервера после окончания архивирования. Для этого в событии OnFinished Launcher'а запускающего архивирование надо прописать следующее:

procedure TForm1.RzLauncher1Finished(Sender: TObject);
begin
RzLauncher3.Launch;//запуск сервера
end;

Тут всё до невозможности просто :) Выполнение холодного копирования практически готово, осталось только настроить Winrar соответствующим образом. Запускай Winrar, заходи в пункт меню Параметры-Установки на закладке Общие поставь галочку Запись протокола ошибок, затем на закладке Архивация в Профилях архивации нажми кнопку Создать по умолчанию, здесь ты можешь указать тип архива по умолчанию, степень сжатия и остальные параметры. Здесь я тебе посоветую только указать на закладке Дополнительно параметр Архивировать в фоновом режиме, при включеном фоновом режиме окна архивирования не будет и соответственно нагрузка на систему снизится, можешь попробовать запустить создание объёмного архива и понажимать кнопку Фоновый режим. Теперь о параметре Запись протокола ошибок, при включённом флажке будет создаваться протокол архивирования в папке с программой rar.log, т.е. если например при архивировании по каким-либо причинам архиватор не смог получить доступ к файлу в этом файле будет записана данная ошибка. Можешь добавить в приложении форму с Memo указав при открытии формы загружать в Memo данные из этого файла, таким образом из приложения ты сможешь просматривать лог архивирования, вот как это получилось у меня:

Конечно ты можешь указать все параметры архивирования в командной строке добавляя их вот сюда через пробел:

Form1.RzLauncher1.Parameters:='a -inul "'+
//параметры архивирования
Form1.RzEdit2.Text + '" "' + Form1.RzEdit1.Text + '"';

Так что смотри справку Winrar'а, там всё написано.

Теперь приступим к программированию экспорта базы данных в файл дампа. На нажатие кнопки указания пути к файлу дампа пропишем следующее:

procedure TForm1.RzButton4Click(Sender: TObject);
begin
if SaveDialog2.Execute then begin
RzEdit10.Text:=SaveDialog2.FileName;
end;
end;

Соответственно я добавил SaveDialog и указал в свойстве FileName - base.dmp. Добавляем новый RzLauncher, в свойстве FileName устанавливаем exp.exe.

Далее изменим процедуру OnTimer следующим образом:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
//установка текущего времени
Present:= Now;
DecodeTime(Present, Hour, Min, Sec, MSec);
 
RzEdit3.Text:=IntToStr(Hour); //edit для отображения часов
RzEdit4.Text:=IntToStr(Min); //edit для отображения минут
RzEdit5.Text:=IntToStr(Sec); //edit для отображения секунд
 
//проверка времени холодного копирования
if (RzEdit3.Text=RzEdit6.Text) and
   (RzEdit4.Text=RzEdit7.Text) and
   (RzEdit5.Text=RzEdit8.Text) then begin
 
//выполнение архивирования
RzLauncher2.Launch;//остановка сервера
end;
 
//проверка времени экспорта
if (RzEdit3.Text=RzEdit11.Text) and
   (RzEdit4.Text=RzEdit12.Text) and
   (RzEdit5.Text=RzEdit13.Text) then begin
 
//выполнение экспорта
RzLauncher4.Parameters:=
'manuf/manuf@manuf file=' + RzEdit10.Text +
' log=export.log owner=manuf';
RzLauncher4.Launch;
end;
end;

Здесь мы добавили процедуру проверки соответствия установленного времени выполнения экспорта текущему времени, запуск утилиты - export, и в качестве параметров для этой утилиты следующие параметры:

manuf/manuf@manuf - строка подключения в серверу, просмотреть параметры соединения можно в утилите Net Manager

file=' + RzEdit10.Text + ' - здесь указываем создаваемый файл дампа (сохранение в выбранную директорию)

log=export.log - здесь указываем лог файл

owner=manuf - здесь указываем владельца

Данная утилита имеет множество параметров, с которыми ты можешь ознакомиться в документации к Oracle или поискать в инете. Нам хватит указанных. Всё создание дампа готово. Можешь опробовать.

Нам осталось только реализовать копирование файла архива БД и файла дампа по сети. Для этого на нажатие кнопки выбора пути для сохранения по сети прописываем следующее:

procedure TForm1.RzButton3Click(Sender: TObject);
begin
if RzSelectFolderDialog1.Execute then begin
RzEdit9.Text:=RzSelectFolderDialog1.SelectedPathName;
end;
end;

Здесь как и при выборе директории баз данных мы вставляем в нужный Edit путь компонента

RzSelectFolderDialog1.

Соответственно в данном поле будет отображаться сетевой путь для сохранения файлов.

Теперь нам необходимо обеспечить подключение сетевого ресурса (например, расшаренной папки), потому что иногда операционная система (сам знаешь какая) иногда теряет подключение и соответственно может не выполниться копирование по сети, чего нам допустить нельзя. Для этого добавим два RzLauncher'а. В свойстве первого FileName укажем disconnect.cmd, второго - connect.cmd. Теперь опять открываем наш любимый Блокнот и прописываем: net use z: /delete, сохраняем в папке с программой под именем disconnect.cmd. Затем изменяем строку на следующее: net use z: 192.168.174.2shara sever /user:ronin, нажимаем Сохранить как и сохраняем в папке с программой под именем connect.cmd.

Объясню всё по порядку: файл disconnet.cmd выполняет отключение сетевого диска z:, на случай его наличия в системе, т.е. например вдруг у тебя уже подключена какая-то сетевая папка под именем диска z:, т.е. мы подстраховываемся, иначе при попытке подключения сетевого ресурса как диска z: и наличия уже подключенного диска z: как сетевого, но например к другой папке, будет выдана ошибка что ресурс занят.

Строка net use z: 192.168.174.2shara sever /user:ronin выполняет подключение расшаренной папки shara расположенной на компьютере с IP адресом 192.168.174.2, пароль для доступа к данной папке sever, имя пользователя ronin. Соответственно у тебя будут другие параметры подключения. Справку по утилите net use ты можешь получить во встроенной справке Windows.

Модернизируем процедуру Execute потока в котором выполняется архивирование БД:

procedure Arhiv.Execute;
Label arh_running;
begin
  { Place thread code here }
if (Form1.RzEdit1.Text<>'') and
   (Form1.RzEdit2.Text<>'')  then begin
 
Form1.RzLauncher1.Parameters:=
'a -inul "'+
Form1.RzEdit2.Text + '" "' + Form1.RzEdit1.Text + '"';
Form1.RzLauncher1.Launch;
end;
//копирование по сети
if Form1.RzEdit9.Text<>'' then begin
//ожидание остановки архивирования
arh_running:
if not(Form1.RzLauncher1.Running) then
CopyFile(PChar(Form1.RzEdit2.Text),PChar(Form1.RzEdit9.Text+'backup.rar'),False)
else goto arh_running;
end;
end;

Здесь мы добавили метку для того чтобы копирование по сети не начиналось пока не будет закончено создение архива, я выбрал этот вариант для того чтобы показать разнообразие методов и приёмов, хотя можно воткнуть выполнение копирования на событие OnFinished RzLauncher'а запускающего архивирование. Для копирования мы воспользовались функцией CopyFile, первым параметром которой идёт файл который надо копировать, вторым куда копировать, третий параметр определяет генерировать ли ошибку если файл существует. И не забудь добавить в раздел uses модуль Windows, иначе не поймёт что такое CopyFile.

Копирование дампа я реализовывать не буду, думаю ты и сам сможешь по аналогии написать данную процедуру.

Ну вот наша программа по резервному копированию готова, можешь выполнять компиляцию, запускать и пробовать

Напоследок хочу высказать "мысли вслух" по поводу реализации программы:

1. Если нет возможности использовать бибилиотеку RAIZE то это конечно печальный факт, но это не значит что нельзя реализовать данные функции по управлению внешними приложениями. Это конечно будет немного сложнее но не невозможно, на просторах сети или в форумах очень много материалов по данной теме, так что дерзай;

2. Вариантов выполнения резервного копирования баз данных Oracle огромное количество, и рассмотрение этих возможностей выходит за рамки данной статьи, так что прийдётся тебе самому осваивать эти способы, хотя чем чёрт не шутит возможно мы рассмотрим эти вопросы в одной из следующих статей

3. Я ещё раз повторюсь: возможно это и не самый удачный вариант реализации резервного копирования, но тоже достойный на существование, я перепробовал много вариантов и программ, включая и встроенные средства в Windows Server EE 2003, ни один из них меня не устроил, и я реализовал резервирование таким вот образом, учитывая что на моём рабочем сервере помимо Oracle крутится база FoxPro;

4. Использование Winrar в данной программе только лишь показательное, вполне возможно заменить её любой другой программой выполняющей резервное копирование и поддерживающую командную строку, например я раньше использовал программу BackUp32, очень популярную и в принципе удобную, но как только база FoxPro (файл-серверная СУБД) перевалила за 5 Гб программа отказалась обрабатывать такое количество файлов, соответственно пришлось бы задуматься об актуализации базы данных, т.е. записи на диск неактуальных данных, таким образом уменьшая размер базы (хотя этим всё равно рано или поздно прийдётся заняться), в отличие от BackUp32 Winrar легко обрабатывает такие архивы;

5. Написав такую программу я обеспечил резервирование баз данных на винчестер на сервере и дополнительно на соседний компьютер в локальной сети, таким образом если вдруг с сервером произойдёт непоправимое (не дай конечно бог), я всегда смогу восстановить по крайней мере вчерашнее состояние баз на любой другой компьютер;

6. Ещё хочу сделать пару замечаний по самой программе. В ней необходимо добавить обработчики исключительных ситуаций, например когда не заполнено какое-нибудь поле, протестировать и отловить все баги, потому что от правильного выполнения данной программы будет зависеть сохранность данных (конечно не на сто процентов). Так в рабочем варианте своей программы я добавил возможность сохранения настроек (путей, времени запуска) в реестре, возможность автозапуска программы а также свёртывание программы в трей. Выполнение всех внешних процессов (архивирование и т.д.) я выкинул в отдельные потоки.

Ну вот думаю всё, можешь наслаждаться результатами своего труда! До встречи!

Written by: Ronin (master_t[]inbox.ru)