Создание сжатых резервных копий базы данных Access

 

Совершенно необходимый алгоритм для выживания в наше беспокойное время засилья китайских компонентов ;) Думаю, никому не надо объяснять, насколько велика ценность резервной копии в условиях краха базы (хотя, за годы работы с многомегабайтными базами Access 2000 были глюки, конечно, но чтобы база данных разрушилась без возможности самовосстановления, такого случая у меня еще не было). Но не будем забывать об аппаратных проблемах, "кривых руках" и ошибках пользователей..

Поделюсь своим способом архивации со сжатием в формат .RAR, который позволяет архивировать со сжатием, уменьшая размер сжатой архивной копии в 9 и более раз. У меня одна 248 Мб база Access 2000 ужимается до 15,6 Мб - я глазам в первый раз не поверил ;), правда, там хранились только данные в таблицах, достаточно однообразные, вот WinRar и выявляет повторяющиеся цепочки... 

У WinRar вообще очень много настроек, за ними отсылаю Вас к его обширной справке и на сайт http://rarlab.com/, а опишу только те, что применяю. С другой стороны, если кого-то интересует лицензионная чистота (все таки Rar не бесплатен), можно использовать любой архиватор, лишь бы он поддерживал управление из командной строки (7-Zip (пример использования ниже), InfoZip и т.п.). Если Вы хотите официально продавать свое творение, внимательно ознакомьтесь с условиями использования - бывает, что архиватор бесплатен только для некоммерческого использования.

На этом закончим правовые вопросы ;) и приступим к изучению кода....

Начну немного издалека...

Дело в том, что опытные разработчики всегда держат данные в одном файле (MDB), а приложение в другом (MDE). Не будем упоминать ADP и SQL (это я особо умным ;). Так легко организовать работу в сети - достаточно разбросать файл-приложение на каждый компьютер в сети и, если необходимо, изменить ссылки на файл с таблицами, лежащим где-нибудь на сервере. Уменьшается нагрузка на сеть, так как все формы, отчеты уже находятся на компьютере клиента, по сети передаются только данные, легко обновлять приложение - достаточно заменять только приложения на новые версии. Не являюсь исключением и я ;) - все проекты поддерживаю в таком виде. 

В моих приложениях есть главная (начальная) форма frmMainMenu, которая открываясь на весь экран, эмулирует красивое пользовательское меню для выбора дальнейших действий. Эта форма никак не связана с таблицами (нет источника записей, не путайте со стандартной кнопочной формой Access!) и, следовательно, не связана с файлом базы данных (с таблицами), т.е. при открытии данной формы файл базы данных еще не занят и доступен для обработки архиватором. Из этого вытекает вывод, что архивировать файл с данными можно только, если к нему нет доступа ни по сети, ни локально - никто больше не открыл файл.  Конечно, это накладывает ограничения на архивацию, но это и единственно правильный путь - совершенно недопустимо архивировать данные во время работы, может, идет индексация, обновление, удаление, да мало ли что в базе может происходить.... Поэтому мы должны вывесить грозное предупреждение перед началом архивации и, возможно, проверить доступность файла. Второе условие - архивирование происходит на компьютере, на котором сами данные и находятся, хотя это и не критично (можно вычислить расположение файла с данными из любого удаленного приложения по путям к таблицам, но я этого не делаю). Чтобы предотвратить потерю данных из-за сбоя жесткого диска, необходимо периодически сбрасывать архивные копии на CD/DVD-RW или на другие компьютеры (вопрос спорный). Но это уже другие, административные проблемы...

Вот такое длинное, но, считаю, необходимое вступление...

Стартовая форма frmMainMenu открывается на компьютерах пользователей мгновенно (нет же еще обращений к данным, лежащим на другом компьютере), закрывает весь экран монитора (независимо от разрешения монитора) и создает рабочую обстановку за счет подобранной экспериментальным путем тональной гаммы ;), некоторой информации о программе, логотипа разработчика и интерактивного меню. Приступим к рассмотрению программного кода:

Option Compare Database
Option Explicit

' API для определения - есть ли файл
Private Declare Function PathFileExists Lib "shlwapi.dll" _
Alias "PathFileExistsA" (ByVal pszPath As String) As Long
' если хотите избежать использования API, проще воспользоваться

' оператором Dir() - ниже в статье приведены примеры

Private Sub LaunchArhiving

' сохраним путь базы
strAppPath = Application.CurrentProject.Path & "\"
' сначала проверим - есть ли файл MyDataBase_Data.mdb он в этом же каталоге лежать должен
' если он есть, тогда это сервер и можно архивировать сам файл базы...
If PathFileExists(strAppPath & "MyDataBase_Data.mdb") = 1 Then
' значит мы сидим на сервере, можно архивировать данные с помощью WinRar
' запуск архивации
    If MsgBox("Перед началом архивации проверьте, " & _
              "чтобы база была закрыта на всех остальных компьютерах в сети." & vbCrLf & _
              "Иначе при попытке архивации возникнет ошибка." & vbCrLf & vbCrLf & _
              "Начинаем архивацию ?", vbExclamation + vbYesNo + vbDefaultButton2, _
              "Архивация данных") = vbYes Then DoCmd.OpenForm ("frmArchiving")
Else
    MsgBox "Архивацию данных необходимо запускать только на компьютере, " & _
           "на котором сама база и находится.", vbInformation, _
           "Архивация данных"
End If
End Sub

Обратите внимание: после проверки не запускается архивация, а открывается форма frmArchiving. Эта форма сделана всплывающей и модальной (лично у меня еще и без заголовка ;), всплывающей в центре экрана. Ее цель - информировать пользователя о том, что идет архивация (пожалуйста, подождите, идет архивация данных, бла, бла, бла ...., красивую анимацию на Flash или видео ;). Ну и конечно, настоящее ее предназначение - запустить и отследить окончание процесса архивации. Вот этот интересный код мы и рассмотрим. Привожу весь код модуля формы, благо, он небольшой, нет смысла его делить на порции:

Option Compare Database
Option Explicit

' для определения завершения программы
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Const STILL_ACTIVE = 259
Const PROCESS_QUERY_INFORMATION = 1024

Private idProg As Long ' удерживаем Handle запущенной нами проги
Private bIsStarted As Boolean

Private Sub Form_Open(Cancel As Integer)
Dim strAppPath As String
' сохраним путь базы
strAppPath = Application.CurrentProject.Path & "\"

' вот тут надо проверить наличие файла WinRar.exe в папке ...\AddOns

' сообщение что не найден архиватор WinRar.exe в папке ...\AddOns и выход

' а также наличие папки ...\Arhives

' сообщение, что не найдена папка \Arhives для хранения архивов и выход
idProg = Shell(strAppPath & "AddOns\WinRAR a -r -m5 -ag_DD-MM-YYYY_HH-MM " & _

         strAppPath & "Arhives\MyDataBase_Data " & strAppPath & _

         "MyDataBase_Data.mdb",vbHide)
bIsStarted = True
End Sub

Private Sub Form_Timer()
Dim IsRunning As Boolean
Dim hProc As Long, iRet As Long

If bIsStarted Then
    ' стартовали и ждем-с, когда закончится внешняя программа
    hProc = OpenProcess(PROCESS_QUERY_INFORMATION, False, idProg)
    If hProc <> vbNull Then GetExitCodeProcess hProc, iRet
    IsRunning = (iRet = STILL_ACTIVE)
    CloseHandle hProc

    If Not IsRunning Then ' процесса больше нет - все закончилось

        DoCmd.Close
    End If
End If
End Sub

Отслеживаем два события формы: Открытие и Таймер (необходимо установить в 1000). Выполняем запуск из подпапки \AddOns исполняемого файла WinRar с ключами командной строки. Обратите внимание на ключ -ag_DD-MM-YYYY_HH-MM, то есть, каждый раз когда мы запускаем архиватор, будет сформирован архив с новой датой, добавленной к имени архива. Так архивы и будут копиться в подпапке  \Arhives. Очень удобно по добавочному суффиксу определять дату и время создания архива, также легко сортировать.

В событии формы Таймер, возникающем каждую секунду (1000), отслеживаем хендл запущенного нами в событии Form_Open процесса. После проверки процесса (все еще активен ?), если процесс уже не активен, то, значит, запущенный нами процесс WinRar.exe закончил работу и мы закрываем форму-предупреждение, если еще активен, то будем дожидаться следующего события таймера. 

Ну и красивое же API :). К сожалению, код этого API не мой, признаюсь, я только внес небольшие поправки, не знаю, кто автор. Работает превосходно под Win2000 и WinXP (под Win95, 98, МЕ не тестировал, ввиду полного неприятия этих нестабильных систем).

Как я и говорил выше, можно с легкостью использовать любой архиватор, поддерживающий командную строку, желательно, чтобы он добавлял к имени архива еще дату и время, как Rar. Хотя, без проблем можно создавать имя архива, используя программный код VBA и "подсовывать" составленное ИмяАрхива & Format (date,"_dd-mm-YYYY_") & Format (time,"_HH-MM") любому архиватору (на примере с 7-Zip ниже). Если Вы рискнете ;) использовать WinRar, то просто положите в папку \AddOns файл WinRar.exe. Больше ничего для работы ему не нужно.

Раз мы тут говорим об архивации, поделюсь еще одним маленьким программерским советом: "накопительная" архивация приложения во время разработки с помощью специальной настройки WinRar. Есть у меня хорошая привычка ;) после нескольких изменений в программе щелкать правой кнопкой мышки на файле приложения в Эксплорере (я предпочитаю ТС) и выбирать из контекстного меню команду WinRar -> Добавить в архив "ИмяФайла.rar". Вся хитрость в том, что в параметрах самого архиватора Rar: "Параметры" -> "Профиль по умолчанию" -> "Резервные копии", я настроил "Добавлять к имени архива окончание по маске _dd-mm-yyyy_hh-mm" и теперь, выбирая данную команду, всегда получаю новый файл архива с добавлением текущей даты и времени создания. Не раз мне приходилось возвращаться назад, и благодаря этому способу я всегда безошибочно выбирал правильную "точку отката". Делаю резервные копии я во время работы очень часто, даже если вношу незначительные изменения в программу, это стало совсем необременительным ;)

Продолжение темы:

Кнопка для выполнения архивации, конечно, хороша, но... дело в том, что пользователи постоянно забывают ее нажимать. Как показала практика, просто забывают, не специально ;). Поэтому я дополнил процедуру выхода из базы напоминанием об архивации. Очень удобно, когда пользователь закрывает программу - ему предлагают сделать архивацию. Тут уж никак не забудешь ;)

Архивация проходит по алгоритму, описанному выше с небольшим дополнением. Проблема в том, что после вызова формы (и запуска архивации) Access сразу выполняет команду Quit. Программа закрывается, а архивация продолжает дорабатывать в фоновом режиме. Вроде бы ничего страшного на первый взгляд.... архив базы нормально создастся (ну разве что будет немного тормозить компьютер какое-то время - WinRar забирает все ресурсы процессора). Но, подумайте, когда закрывают программу - значит обычно выключают компьютер! Пользователь не ждет окончания (и не знает), что там идет фоновый процесс - он просто выключает компьютер и .... создание архива тоже обрывается (понятно, какова ценность такого архива).

Сначала я попробовал выдавать предупреждающее сообщение - подождите минуту .... бла, бла, бла. Какую минуту, когда можно - все это было очень неочевидно - смотреть на индикатор дисковой активности.... да... Пришлось задуматься над более правильной реализацией и, как всегда, было найдено простое решение ;)

Вот так я сначала написал... И какие проблемы возникли, вы уже знаете.

DoCmd.OpenForm ("frmArchiving")

DoCmd.Quit

а "правильная" реализация будет вот такая:

DoCmd.OpenForm ("frmArchiving"), acNormal, , , , acDialog

DoCmd.Quit

хитрость в том, что форма frmArchiving открывается как диалоговое окно и Access не может выполнить следующую строку кода, пока диалоговое окно не будет закрыто. Это-то нам и необходимо! Дожидаемся окончания архивного процесса и закрытия формы frmArchiving. Вот только после этого Access и приступает к выполнению строки DoCmd.Quit.

Последний штрих: в режиме разработки в свойствах формы уберите кнопку оконного меню и кнопку закрытия, задайте понятную подпись формы и у Вас будет открываться форма frmArchiving в диалоговом режиме без возможности закрытия нетерпеливым пользователем ;)

Идет дальше. Как я уже писал выше приложение и данные у меня хранятся отдельно, база лежит на сервере, там тоже идет работа, а клиентские приложения разбросаны на машинах пользователей. Клиентское приложение одно и нет разницы, работает ли оно на сервере или на клиентской машине. Я веду к тому, что необходима проверка - лежит ли файл MyDataBase_Data.mdb в той же папке, что и приложение. Если лежит, то это сервер и можно делать архивацию. Все это уже было описаны выше, я просто освежаю память ;). Также необходимо проверять наличие файла-блокировки MyDataBase_Data.ldb. Если этот файл существует - значит база открыта с какой-то машины, неважно с какой, все равно архивацию производить нельзя (да и не получится). Поэтому, даже и не заикаемся об архивации и выходим молча ;). Если же условия для создания архивной копии благоприятны, тогда задаем вопрос-напоминание. Я полагаю, такой вопрос необходим, а вдруг пожар и необходимо срочно выключить компьютер ;). Шутка, конечно.

Вот и весь код кнопки выхода:

'На выходе (нажатие кнопки Выход)
'Проверяем наличие файла MyDataBase_Data.mdb если есть то
'Проверяем наличие файла MyDataBase_Data.ldb если его нет то
'предлагаем перед выход сделать архивацию, если согласен то, архивация и выход.......
If PathFileExists(strAppPath & "MyDataBase_Data.mdb") = 1 Then ' значит, файл существует
    If PathFileExists(strAppPath & "MyDataBase_Data.ldb") <> 1 Then

      ' значит, файл блокировки не существует - можно архивировать базу
        If MsgBox("Не забывайте создавать архивную копию базы данных при завершении работы." & vbCrLf & vbCrLf & _
            "Создать архивную копию базы сейчас ?", vbInformation + vbYesNo + vbDefaultButton2, _
            "Архивация данных") = vbYes Then
            DoCmd.OpenForm ("frmArchiving"), acNormal, , , , acDialog
        End If
    End If
End If
DoCmd.Quit

Мысли в дополнение : так как архивы создаются на одной машине (обычно она имеет и один диск), предусмотрите возможность резервного копирования-синхронизации созданных архивов на другие машины в сети или на другие диски, если у Вас не RAID-массив. Это уже вопрос администрирования, возможно, вопрос безопасности. Синхронизацию легко и быстро можно организовать, например, с помощью бесплатной (для жителей USSR) программы nnBackUp http://www.nncron.ru/, вставив вызов программы под учетной записью администратора в реестр (HKLM...Run) других компьютеров сети (тогда пользователи с правами Пользователь не будут видеть консольного окна выполнения программы и не смогут повлиять на сам процесс синхронизации и получать доступ к папкам архивов, к которым возможен доступ только с правами администратора). Этот способ реализован лично и могу утверждать, что синхронизатор nnBackUp работает быстро и стабильно.

Практический опыт применения: в общем-то описанная выше идея хороша своей академичностью ;). Но опробовав на практике вышеизложенный код, я пришел к выводу, что код необходимо упрощать, и сильно! ;)

Размышляя над упрощением кода, я пришел к такому результату.

В событие стартовой формы (той, что у меня всегда открыта на экране, как бекграунд) Form_Unload добавляем следующий код:

Private Sub Form_Unload(Cancel As Integer)
Dim strAppPath As String
Dim idProg As Long
Dim DirectoryFound As String ' для проверки существования папки

'сохраним путь базы
strAppPath = Application.CurrentProject.Path & "\"
'На выходе из программы -
'Проверяем наличие файла Нарушения ПДД_data.mdb, если есть, то
'Проверяем наличие файла Нарушения ПДД_data.ldb, если его нет, то
'предлагаем перед выход сделать архивацию, если согласен то, архивация и выход.......
If Dir(strAppPath & "Нарушения_ПДД_data.mdb") <> "" Then

' значит, файл Нарушения_ПДД_data.mdb существует - это сервер!
    If Dir(strAppPath & "Нарушения_ПДД_data.ldb") = "" Then

    ' значит, файл блокировки не существует - никто не занимает файл
        If MsgBox("Не забывайте создавать архивную копию базы данных при завершении работы." & vbCrLf & vbCrLf & _
            "Создать архивную копию базы сейчас ?", vbInformation + vbYesNo, _
            "Архивация данных после завершения работы") = vbYes Then
            'проверить, есть ли файл WinRar.exe в папке \AddOns
            If Dir(strAppPath & "AddOns\WinRAR.exe") = "" Then
                MsgBox "Не найден архиватор WinRar.exe в подпапке \AddOns!", vbCritical,    "Невозможно начать архивацию данных"
                Exit Sub
            End If
            'проверить наличие папки \Archives
            DirectoryFound = Dir(strAppPath & "Arhives", vbDirectory)
            If (Len(DirectoryFound) = 0 Or Err = 76) Then 'errPathNotFound = 76
                MsgBox "Не найдена папка \Arhives для размещения архива", vbCritical, "Не найдена папка для архивов"
                Exit Sub
            End If
            idProg = Shell(strAppPath & "AddOns\WinRAR a -r -m5 -ag_DD-MM-YYYY_HH-MM " & strAppPath & "Arhives\Нарушения_ПДД_data " & strAppPath & "Нарушения_ПДД_data.mdb", vbNormalFocus)
        End If
    End If
End If
End Sub
 

Этот код дает нам сплошные преимущества:

Во первых: визуализация процесса архивации, который можно и притормозить и отменить (сама база и приложение уже закрылось, на экране остается только окошко архиватора) - юзер не ерзает на стуле в догадках, когда же кончится весь этот странный процесс ;), во-вторых: в папку, где лежит WinRar.exe, положите также файл справки WinRAR.hlp - юзер может почитать отличное описание процесса архивации во время архивации, щелкнув на кнопке [Справка] (советую и Вам почитать;), в третьих: значительно упростился программный код и обратите внимание - весь процесс перенесен в событие Unload (стартовой) формы - т.е. неважно, как пользователь закрывает приложение - крестиком в правом верхнем углу формы или предусмотренной кнопкой выхода, вопроса (но только при благоприятных для архивации условиях!) ему все равно не избежать ;).

Да и еще WinRar - прекрасный архиватор, может быть, самый лучший, но не бесплатный, поэтому всем будет хорошо ;), если Вы будете использовать легально приобретенную версию.

Пример с использованием бесплатного архиватора 7-Zip 4.13 beta, как я и грозился ;) Надо сказать, что отличный архиватор 7-Zip http://www.7-zip.org/, написанный Игорем Павловым, распространяется свободно и Вы также можете свободно применять его в Ваших разработках.

В этом примере я использую сжатие в стандартный Zip (что думаю, вполне оправдано, учитывая поддержку этого стандарта в самой Windows XP) методом сжатия Normal - очень быстро и достаточно сильно - 48 Мб сжимаются до 6.2 Мб. (К слову, WinRar по примеру выше делает из 48 Мб 4.3 Мб, но там режим сжатия выставлен на максимум). Если режим сжатия выставить на максимум и в 7-Zip, тогда архив уменьшается до 6 Mb, но значительно увеличивается время создания архива - все-таки я решил остановиться на нормальном методе сжатия Zip.

Есть два пути использования архиватора 7-Zip:

первый способ : если у Вас уже установлен этот архиватор, то просто скопируйте файл 7zg.exe из папки архиватора (по умолчанию "C:\Program Files\7-Zip\..") в папку AddOns\ базы данных. Ну и в настройках архиватора можно переключиться на русский язык, если хотите.

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

в папку AddOns переписал файл 7zg.exe

в папке AddOns создал подпапку Codecs и переписал туда файл Deflate.dll

в папке AddOns создал подпапку Formats и переписал туда файл zip.dll

это минимум, что требуется архиватору 7-Zip для сжатия в Zip (никакой дополнительной регистрации компонентов проводить не нужно).

Для удобства русскоязычных пользователей можно еще в папку AddOns положить файл ru.txt - русификатор 7-Zip (для удобства, чтобы не было неоднозначности, я переименовал этот файл русификатора в 7Zip-ru.txt) и в реестре по адресу: HKEY_CURRENT_USER\Software\7-ZIP указать в строковом параметре точный путь к файлу-русификатору: у меня С:\PDD\AddOns\7Zip-ru.txt. Если этого не сделать, что интерфейс будет инглиш ;)

После таких манипуляций можно приступать к изучению кода - считайте, что минимальный 7-Zip уже "инсталлирован" на машине.

За основу возьмем последний код, где в качестве архиватора используется WinRar и "переточим" его под 7-Zip:

Private Sub Form_Unload(Cancel As Integer)
Dim strAppPath As String
Dim idProg As Long
Dim DirectoryFound As String ' для проверки существования папки

'сохраним путь базы
strAppPath = Application.CurrentProject.Path & "\"
'На выходе из программы -
'Проверяем наличие файла Нарушения ПДД_data.mdb, если есть, то
'Проверяем наличие файла Нарушения ПДД_data.ldb, если его нет, то
'предлагаем перед выход сделать архивацию, если согласен то, архивация и выход.......
If Dir(strAppPath & "Нарушения_ПДД_data.mdb") <> "" Then

' значит, файл Нарушения_ПДД_data.mdb существует - это сервер!
    If Dir(strAppPath & "Нарушения_ПДД_data.ldb") = "" Then

    ' значит, файл блокировки не существует - никто не занимает файл
        If MsgBox("Не забывайте создавать архивную копию базы данных при завершении работы." & vbCrLf & vbCrLf & _
            "Создать архивную копию базы сейчас ?", vbInformation + vbYesNo, _
            "Архивация данных после завершения работы") = vbYes Then
            'проверить, есть ли файл 7zg.exe в папке \AddOns
            If Dir(strAppPath & "AddOns\7zg.exe") = "" Then
                MsgBox "Не найден архиватор 7zg.exe в подпапке \AddOns!", vbCritical,    "Невозможно начать архивацию данных"
                Exit Sub
            End If
            'проверить наличие папки \Archives
            DirectoryFound = Dir(strAppPath & "Arhives", vbDirectory)
            If (Len(DirectoryFound) = 0 Or Err = 76) Then 'errPathNotFound = 76
                MsgBox "Не найдена папка \Arhives для размещения архива", vbCritical, "Не найдена папка для архивов"
                Exit Sub
            End If

            ' запуск архиватора
            idProg = Shell(strAppPath & "AddOns\7zg.exe a -tzip " & strAppPath & "Arhives\Нарушения_ПДД_data" & Format (date,"_dd-mm-YYYY_") & Format (time,"_HH-MM")  & ".zip " & strAppPath & "Нарушения_ПДД_data.mdb", vbNormalFocus)
        End If
    End If
End If
End Sub
 

Вот и все. Теперь Вы с чистой совестью можете эксплуатировать архивацию на базе 7-Zip, никто Вам и слова плохого не скажет ;)


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=7533