А Ковязин - Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil
Итак, чтобы приступить к переносу данных из старой базы данных, надо сначала создать базу-приемник. Если база данных устоявшаяся (метаданные давно не менялись), то для создания базы данных-приемника можно воспользоваться какой-нибудь старой backup-копией, из которой можно извлечь метаданные (для этого лучше всего воспользоваться какой-нибудь утилитой администрирования). На основе этих метаданных необходимо создать базу данных-приемник и приступить к перекачиванию данных. В принципе главное - это вытащить данные из поврежденной базы данных, а размещение этих данные в новой базе является задачей менее трудоемкой и сложной, даже если придется восстанавливать структуру базы данных "по памяти".
При извлечении данных из таблиц следует пользоваться следующим алгоритмом действий:
* Сначала нужно попытаться выполнить SELECT * FROM tableN. Если это прошло нормально, то вы можете сохранить полученные данные во внешнем источнике. Для этой цели хорошо подходит сохранение данных в скрипт (такую функцию предоставляют почти все графические утилиты администрирования), если только таблица не содержит BLOB-полей. Если в таблице есть BLOB-поля, то данные из них необходимо сохранять в другую базу данных с помощью программы-клиента, которая будет исполнять роль посредника. Возможно, вам придется написать эту тривиальную программу специально для целей восстановления данных.
* Если запрос на выборку всех данных "не прошел", то необходимо удалить все индексы и повторить запрос. В сущности, индексы можно удалить у всех таблиц с самого начала восстановления - они больше не понадобятся. Естественно, если у вас нет структуры метаданных, идентичной поврежденной, необходимо вести протокол всех действий, которые вы проделываете над поврежденной базой данных-источником.
* Если после удаления индексов не удается прочитать все данные из таблицы, то можно попробовать делать выборки с интервалами по первичному ключу, - т. е. выбирать определенные диапазоны данных, например:
SELECT * FROM tableN WHERE field_PK>=0 and field_PK <=10000
Здесь field_PK - поле, которое исполняет роль первичного ключа. Так как InterBase имеет страничную организацию данных, то выборка диапазонов значений может оказаться достаточно эффективной, хотя это и кажется чем-то вроде шаманства. Тем не менее это работает, поскольку при этом мы можем исключить из выборки данные с поврежденных страниц, а остальные успешно прочитать. Вы можете вспомнить наш тезис о том, что в SQL нет определенного порядка хранения записей. Да, действительно, никто не гарантирует, что неупорядоченная выборка при повторных запусках вернет нам записи в одинаковом порядке, но тем не менее физически записи хранятся внутри базы данных в определенном внутреннем порядке. Очевидно, что сервер не станет "перетасовывать" записи просто ради следования букве SQL-стандартов. Этим внутренним порядком можно попытаться воспользоваться, извлекая данные из поврежденной базы данных (подробнее о страницах данных и их взаимосвязях см. ниже в главе "Структура базы данных InterBase"). Виталий Бармин, один из опытных российских InterBase-разработчиков, сообщал о том, что таким образом ему удалось извлечь из сильно попорченной базы данных, в которой было множество поврежденных страниц, до 98% информации!
Таким образом, данные из поврежденной базы данных необходимо перенести в новую базу или же просто во внешние источники данных, наподобие sql- скриптов. При копировании данных обратите внимание на значения генераторов в поврежденной базе - их необходимо сохранить для возобновления корректной работы в новой базе данных. Если у вас отсутствует полная копия метаданных, то необходимо, хотя бы частично, извлечь из поврежденной базы данных тексты хранимых процедур, триггеров, ограничений и определения индексов.
Восстановление "безнадежных" баз данных. InterBase Surgeon
В общем, восстановление базы данных может быть очень хлопотным и тяжелым делом, поэтому никогда не бывает лишним сделать резервную копию базы данных, чтобы не заниматься восстановлением поврежденных данных. Тем не менее, даже если это и случилось, не стоит отчаиваться - выход можно найти даже в самых, казалось бы, безнадежных ситуациях, и сейчас мы рассмотрим два таких случая.
Первый случай представляет собой классическую проблему - это невосстанавливающаяся резервная копия из-за наличия NULL-значений в столбце с ограничениями NOT NULL, которую пустили на восстановление прямо поверх рабочего файла. Рабочий файл затерся, процесс восстановления (restore) прервался из-за ошибки, и мы получили в результате необдуманных действий большой кусок бесполезных данных вместо резервной копии, который лежит на диске и, кажется, не поддается восстановлению. Но выход все же был найден. В той реальной ситуации, в которой мы нашли данное решение, программист сумел вспомнить, на какую именно таблицу и на какой столбец было наложено злополучное ограничение NOT NULL. Файл резервной копии был загружен в шестнадцатеричный редактор, и там путем поиска было найдено сочетание байтов, соответствующее определению этого столбца. После многочисленных экспериментов выяснилось, что ограничение NOT NULL добавляет единичку "где-то рядом" с именем столбца. Прямой правкой в НЕХ-редакторе эта единичка была исправлена на 0, и резервная копия была восстановлена. Программист отделался легким испугом и навсегда запомнил, как правильно организовывать процесс резервного копирования и восстановления из резервной копии.
Следующий сличай казался совершенно безнадежным, однако выход все же был найден.
Ситуация сложилась катастрофическая. База данных "сломалась" на этапе расширения из-за нехватки дискового пространства. InterBase записал на странице учета страниц число страниц, превышающее ее реальный размер. Более того, на страницах учета страниц, похоже, также образовались повреждения. В результате база данных вообще не открывалась ни средствами администрирования, ни утилитой gbak, а при попытке подключиться к базе данных появлялась ошибка "Unexpected end of file". При запуске утилиты gfix происходили весьма странные вещи, программа попросту входила в бесконечный цикл. Сервер в момент работы gfix с огромной скоростью (около 100 Кбайт/с) писал ошибки в лог (файл interbase.log). В результате файл лога очень быстро заполнял все свободное дисковое пространство, и пришлось даже написать программу, которая по таймеру уничтожала неимоверно распухающий лог. Этот процесс продолжался довольно долго - gfix работал более 16 ч без каких-либо видимых результатов.
Лог наполнялся ошибками вида "Page XXXX doubly allocated". В исходных кодах InterBase (в файле val.c) есть скупое описание этой ошибки. Оно гласит, что данная ошибка возникает в случае, когда одна и та же страница данных используется дважды. Очевидно, что эта ошибка, как и зацикливание, являлась следствием разрх тения критически важных страниц в базе данных В результате после нескольких дней безуспешных экспериментов попытки восстановить данные стандартными (документированными) способами были оставлены Поэтому пришлось перейти к низкоуровневому анализу данных, хранящихся в поврежденной базе данных, точнее, в том неупорядоченном массиве данных, который oт нее остался.
Автором идеи о том, как извлечь информацию из подобных "безнадежных" баз данных является Александр Козельский, начальник отдела информационных технологий компании East View Publications Inc. (www.eastview.com).
Метод восстановления, полученный в результате исследований, основывался на том факте, что база данных имеет страничную организацию, а данные из каждой таблицы сгруппированы по страницам данных. Каждая страница данных содержит идентификатор таблицы, для которой она хранит данные.
Особенно было важно восстановить данные из нескольких критичных таблиц. Существовали данные из таких же таблиц, полученные из старой резервной копии, которая отлично работала и могла служить образцом.
База данных-образец была загружена в редактор шестнадцатеричных кодов, затем был произведен поиск образцов тех данных, которые нас интересовали. Шестнадцатеричное представление этих данных было скопировано в буфер, а затем в редактор загрузили остатки поврежденной базы данных. В поврежденной базе данных была найдена последовательность байтов, соответствующая образцу, и была проанализирована страница, на которой нашлась эта последовательность. Сначала было определено начало страницы, что оказалось несложно, поскольку размер файла базы данных кратен размеру страницы данных. Найти начало страницы также оказалось возможным, для этого пришлось поделить номер текущего байта на размер страницы - 8192 байта, а затем округлить результат до целых (в результате получился номер текущей страницы) и умножить полученное число на размер страницы. Таким образом, был найден номер байта, соответствующий началу текущей страницы. Проанализировав заголовок, определили тип страницы (для страниц с данными тип равен пяти - см. файл ods.h из комплекта исходных кодов InterBase, а также ниже главу "Структура базы данных InterBase"), а также идентификатор нужной таблицы.