Причины повреждений баз данных. Как починить базу данных InterBase или Firebird?

KDV, 03.09.2002, исправления и дополнения – 25.01.2003, 06.02.2003, 15.04.2003, 16.05.2003, 02.09.2003, 26.11.2003, 03.02.2004, 06.09.2004, 17.02.2005, 05.07.2007 11.10.2007, 2015.10.11.

Если Вам нужно действительно починить БД Firebird

Самый быстрый и надежный способ починить серьёзно поврежденную БД относительно небольшого размера (до 65Гб) – это использовать инструмент IBSurgeon FirstAID, который может починить практически все повреждения БД и спасти оставшиеся данные.
 
Скачайте FirstAID прямо сейчас и следуйте инструкциям из Руководства по починке баз данных.
Если размер Вашей БД больше 65Гб, то обратитесь в нашу службу техподдержки support@ibase.ru.

      Загрузить FirstAID      


Содержание

 

Первый закон Чизхолма: Все, что может испортиться, портится.
Следствие: Все, что не может испортиться, портится тоже.

Введение

На эту тему можно было бы процитировать половину законов Мэрфи. Вместо этого я процитирую начало статьи в журнале Intelligent Enterprise

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

Когда я читал эту статью, меня больше всего потрясло именно второе предложение в этом абзаце. К сожалению, это грубая правда жизни. И здесь под "предупредительными мерами" ни в коем случае нельзя понимать "покупку техники brand-name". Как показала дискуссия в конференции epsylon.public.interbase, "брэнды" тоже ломаются, иногда даже чаще чем "самосборные машины". Поэтому "предупредительные меры"  это не только качественное "железо", но и планирование резервного копирования данных.

Планирование-планированием, но вот еще одна цитата, уже из письма:

"... днем сдох IBM-ер у моих "ну очень уважаемых клиентов", а вместе с ним и БД акционерок и акционеров, с общим объемом уставных фондов около полуярда гривень (около 100 млн вечно-зеленых гульденов). Резервация ее делалась на RW-диск, отформатированный под UDF. Но кто-то (никто не признался) вставил вместо него неотформатированную RW. И так она пролежала там на протяжении последнего месяца. Как раз в октябре/ноябре у них шел сплошной поток обновлений в БД...."

О технике (железе) здесь будет сказано дальше, но немного. Основной же целью этой статьи является перечисление типов повреждений базы данных, способы ремонта, а также предотвращения порчи баз данных. Обычно при сбое базы данных ее можно починить утилитой gfix. Однако, иногда этот инструмент не помогает. В таких случаях нужно обратиться в конференцию, где попытаются помочь. В случае больших баз данных нужно быть готовым при починке полагаться только на себя, поскольку вряд ли получится отправить ~>1Гб базу (имеется в виду размер архива zip или rar, конечно) данных специалистам по email или на ftp. И нужно знать, что бывают случаи, когда базу данных восстановить не удастся никаким образом. Поэтому никогда не забывайте своевременно делать backup, а также проводить "контрольный" restore! (Разумеется, никогда нельзя делать restore на место оригинальной базы данных, т. е. с ключом -r. Вы рискуете остаться без базы данных).

Для ознакомления – перечень наиболее частых ситуаций, для которых компания iBase осуществляет платный ремонт БД. (Это не означает, что можно починить все – недавно базу со сбоем N2 починить не смогли, т. к. оказались затертыми не только половина данных, но и информация в системных таблицах о структуре записей). Если gfix не чинит базу данных, то в 70% случаев простых повреждений может помочь утилита IBSurgeon FirstAid. Для оценки повреждений БД имеет смысл сначала проверить БД с помощью бесплатного режима (с возможностью предпросмотра данных) FirstAID и прислать лог (в zip) на support@ibase.ru, т. к. может оказаться, что ваш случай попадает в худшие 30%. В любом случае не стоит надеяться на появление инструмента, который из любой самой убитой базы данных сможет вытащить ваши данные.

Если база убита, а есть только backup, и он тоже поврежден, или по иным причинам нужно извлечь из backup только часть данных – к вашим услугам инструмент IBBackupSurgeon. (FirstAid и IBBackupSurgeon – платные. Их можно купить).
 
Примечание. Чаще всего обращения о ремонте БД содержат не абстрактную просьбу "починить БД", а желание вытащить из поврежденной БД какие-то жизненно важные данные. Как раз этот случай зачастую требует именно ручного ремонта, поскольку автоматизируемый ремонт как gfix так и FirstAID старается отремонтировать БД для 100% возможности ее бэкапа, но не факт, что сделано это будет с максимальной сохранностью данных (см. Ремонт БД, примечание к пункту 5).
Примечание. Исследовать структуру БД самостоятельно можно при помощи утилиты IBSurgeon Viewer.


Повреждения базы данных

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

Отключение питания сервера

Самый частый случай повреждения базы данных это отключение питания на сервере. Такие ситуации нужно пытаться предотвращать, используя аппаратные средства (UPS, RAID-контроллеры с батарейками). InterBase имеет два режима записи страниц – синхронный и асинхронный. Для всех версий до 6.0 создаваемые базы данных имели по умолчанию синхронный режим (Firebird v1.0 также имеет этот режим включенным по умолчанию). Для изменения режима можно воспользоваться утилитой gfix.

Включение синхронного режима:
gfix -write sync database.gdb
Включение асинхронного режима:
gfix -write async database.gdb

Синхронная запись означает, что измененные страницы базы данных не будут кэшироваться операционной системой, а будут записываться непосредственно на диск при выталкивании страниц из кэша на запись (на Windows это в буквальном смысле отсутствие флага lazy write при открытии файла БД). Это ухудшает производительность, поэтому большинство людей выключают forced writes. В этом случае измененные страницы находятся в кэше операционной системы до тех пор, пока операционная система не решит записать их на диск. В некоторых случаях при непрерывной работе с БД операционная система не сбрасывала измененные страницы на диск до тех пор, пока все пользователи не отсоединялись от базы данных. Понятно, что при выключении питания в этом случае повреждения базы данных могут быть максимальными. Причем, повреждения в данном случае происходят от "недозаписи" информации. Это куда менее печальный случай, чем "перезапись" информации случайными данными, о чем пойдет речь в следующем разделе. Однако, на Windows было обнаружено, что если у базы данных установлено Forced Write = Off, то при определенных условиях измененные страницы БД могли неделями не попадать в БД, и оставаться в кэше операционной системы. При этом, в случае сбоя сервера (или отключения питания), пропадало огромное количество изменений в БД (а база могла выглядеть вообще неповрежденной). То есть, БД как бы оказывалась "неизменяемой" в течение длительного времени. Для исключения данной проблемы в Firebird 1.5 были введены дополнительные параметры для принудительного сброса кэша операционной системы при ForcedWrite=Off (см. параметры MaxUnflushedWrites и MaxUnflushedWriteTime в firebird.conf).
 
Примечание. На современных дисковых системах включение Forced Write практически не влияет на производительность. Это заметно разве что на desktop-системах с IDE-дисками (старыми). Хотя вообще кэширование записи операционной системой дает выигрыш в производительности при массовых обновлениях данных.

Дефекты оборудования

Память

Самый частый дефект за последние полтора года – сбои памяти (RAM) (случаи такого рода сильно сократились в 2005 году). Очевидно, при использовании серверов "своей сборки" приобретается память подешевле, что приводит к соответствующим результатам. Желательно для сервера приобретать и материнскую плату и память с поддержкой ECC.Сбои памяти могут привести к достаточно тяжелым последствиям, которые описаны дальше в этом разделе, в главе "невосстанавливаемый backup". Сервер не только "пропускает" страницы базы данных через память, но и кэширует их в памяти. Поскольку контрольные суммы страниц были отключены еще в IB 5.0, повреждения обнаруживаются только когда происходит чтение данных с диска. Собственно, контрольные суммы страниц, даже если бы они и были, не помогут когда сервер будет записывать страницу на диск через сбойный участок памяти. В противном случае данные пришлось бы перечитывать, что весьма серьезно ухудшило бы производительность.Сбои памяти еще плохи тем, что в этом случае поврежденными как правило оказываются и база данных и ее shadow, если shadow используется в качестве "быстрой резервной копии" (т. к. запись на диск идет из одних и тех же участков памяти).

Диски

Упоминавшиеся выше контрольные суммы страниц данных были исключены в сервере IB 5.0 не зря. Дело в том, что эти контрольные суммы фактически дублировали контроль данных, который должен производиться дисковым накопителем. Раньше, лет 10-15 назад, bad-блоки появлялись часто, и существовали специальные утилиты для их исправления. Сейчас контроль ошибок не только может исправить данные на поврежденном блоке самостоятельно, но и прозрачно сохранит блок в рабочем месте диска, а плохой блок пометит к исключению из дальнейшего использования. Грубо говоря, нынешние диски либо работают, либо не работают целиком.

Контроллеры

Здесь можно отметить только то, что при сбое в работе контроллера некорректная информация может быть записана на все носители, которые подключены к этому контроллеру. Именно поэтому при организации shadow рекомендуется располагать ее на винчестере, подключенном к другому контроллеру.

Другие программы

InterBase, Firebird или Yaffil не работают с операционной системой на "внутреннем" уровне. Это обычное приложение, которое никогда не может вызвать сбой типа известного "синего экрана" в Windows NT. Поэтому если подобный сбой ОС произошел, в этом скорее виноваты некорректно работающие драйверы, другие программы или само оборудование (очень часто в "синем экране" виноваты драйвера видео).
 
Примечание. В W2K по умолчанию при сбое ОС происходит рестарт системы. Если хотите видеть, что система зависла, в My Computer/Properties, Advanced, Startup and Recovery уберите чекбокс Automatically reboot.
В предыдущем разделе был приведен пример повреждения БД, когда не вся новая информация записана в БД. При сбоях ОС может случиться обратная ситуация – данные могут продолжать записываться в файл БД при "зависании" ОС, однако это могут быть уже совсем не те данные, которые собирался записать Interbase в файл базы данных. В этом случае повреждения оказываются наиболее серьезными, которые редко могут быть исправлены при помощи gfix.

Сбои самого сервера

Разумеется, InterBase (Firebird, Yaffil) как и любое другое ПО не является идеальной программой. Идеальной, конечно, в смысле отсутствия ошибок. Если "железо" работает нормально, сервер может сам "сломаться" и либо просто перестать работать, либо испортить базу данных. Конкретный случай последних 1.5-2-х лет – превышение размера в 4 гигабайта файлом базы данных при использовании старых версий InterBase (или ранних версий Firebird на определенных платформах). Раньше, и в том числе в 5.x (1997-2000 годы), код сервера содержал вызов обычной функции позиционирования по файлу БД (seek), которая не могла адресовать более 4-гигабайт (в те далекие времена просто не было файловых систем, которые поддерживали файлы больше 4-х гигабайт). Когда в функцию передавалось такое большое число, оно обрезалось по старшим разрядам. Происходила такая ситуация при операции расширения файла БД, т. е. при записи новых страниц, а следовательно файл БД оказывался "затертым" новой информации с самого начала, т. е. с нулевой страницы (страница заголовка БД). Если новых страниц к записи было много, то уничтожалась начальная часть БД, где как правило содержатся системные таблицы, страницы информации о транзакциях и т. п. Причем борьба с пресловутым размером файла в 4 гигабайта дольше всего велась на Linux, что связано не только с кодом СУБД, но и с поддержкой файлов таких размеров самой операционной системой и ее файловыми системами. Firebird исправил эту проблему окончательно только в версии 1.0.2, причем 1.0.2 выпускался как в старом варианте, так и с 64bit-IO. Borland также не миновала чаша сия, и для IB 7 выпущен патч (7.0.1), который исправлял проблему с размером файла для Linux. Firebird для FreeBSD поддерживает файлы более 4 гигабайт начиная с версии 1.5.

На Windows в IB7, Firebird и Yaffil этой проблемы уже нет, т. е. файл БД может иметь размер и 10, и 20 и больше гигабайт.

В любом случае, при работе на Unix или Windows следует внимательно изучить возможности ядра и конкретной (используемой) файловой системы – способны ли они работать с файлами больше 4-х гигабайт, а также проверить версию IB/FB/YA, чтобы быть уверенным в корректной работе с такими файлами, или наоборот, сразу предусмотреть разбиение БД на многофайловую, например, "кусками" по 2-3 гигабайта.

В отношении файловых систем Windows известна следующая особенность: на FAT32 поддерживаются файлы размером не более 4 гигабайт (чаще всего указанное повреждение БД и происходит при использовании этой, фактически уже устаревшей, файловой системы). Допустим, размер вашей БД достиг 3-х гигабайт, и вы хотите перенести ее на NTFS, чтобы избежать ограничения в 4 Гб. Проблема в том, что с FAT32 на NTFS скопируется только 2 гигабайта из 3-х, из-за особенностей Windows. Это еще раз подчеркивает необходимость знания ограничений используемых файловых систем и их взаимодействия на одном компьютере. На текущий момент все последние версии InterBase, Firebird и Yaffil не имеют ограничений по размеру файла БД, для любых операционных систем. В остальных случаях, одной из характерных ошибок, которую наблюдают разработчики, является "cannot continue after bugcheck(xxx)" с любым номером ошибки. Это означает, что сервер во время работы перешел в такое состояние, что дальнейшая работа с базой данных может ее повредить. При этом рекомендуется рестартовать сервер, после чего желательно проверить базу данных утилитой GFIX.
 
Замечание. Сообщение bugcheck не имеет ничего общего с ошибками (багами, bugs), обнаруживаемыми и регулярно исправляемыми в коде сервера.

Остановка во время сборки мусора

Когда исходный код Interbase был опубликован, оказалась выявлена еще одна неприятная особенность, которая может привести к серьезным повреждениям базы данных. Если во время принудительного завершения работы сервера (gfix -shut ...) были активные подключения и сервер занимался сборкой мусора (работал sweep thread), то база данных может быть повреждена (и чаще всего это так и происходит). Уменьшить вероятность таких повреждений можно только отключив автоматическую сборку мусора (gfix bd.gdb -housekeeping 0), а в случае принудительной сборки мусора (gfix -sweep) предварительно делать "быстрый" backup (gbak с ключом -g, то есть без сборки мусора), чтобы резервная копия базы данных оказалась самой свежей в случае сбоя и повреждения БД. В Yaffil эта проблема исправлена.

Повреждения индексов

Повреждения индексов могут происходить как по всем вышеперечисленным причинам, так и из-за ряда багов сервера при работе с индексами (исправлены в Firebird 1.5, Yaffil). Поскольку индексы не являются 100% необходимым видом объектов для функционирования базы данных, их повреждения обнаруживаются значительно позже (если администратор смотрит в interbase.log), чем повреждения других объектов БД (например, данных таблиц). И клиентские приложения могут продолжать функционировать в такой ситуации как и прежде.

Однако, при повреждении индексов возможно искажение данных, получаемых приложениями. Если в индексе повреждено несколько ключей, и сервер не выдал сообщения об ошибке при выполнении запроса, использующего такой индекс, в результат запроса не попадут записи, на которые ссылаются те самые поврежденные ключи. То есть, часть записей может "пропасть". Обнаружить разницу в выдаваемом количестве записей можно только используя запросы с полным перебором записей
SELECT * FROM TABLE
и с перебором по индексу
SELECT * FROM TABLE
WHERE FIELD > 0
где FIELD  столбец, по которому есть возможно поврежденный индекс, а > 0 – условие, которое однозначно будет выбирать все записи.
(разумеется, лучше этого не делать, а при подозрении на "пропадание записей" сразу посмотреть в interbase.log, перестроить те индексы, о повреждениях которых там сообщается.

В interbase.log пишутся только порядковые номера индексов (а не их имена) для конкретных таблиц, как это и указано в rdb$indices). Процесс backup поврежденные или неповрежденные индексы (за исключением повреждений индексов по системным таблицам) не интересуют, т. к. индексы в backup хранятся только в виде описания в системных таблицах (restore создает индексы по этим описаниям в самом конце процесса restore). Backup считывает записи в натуральном порядке и индексы не использует, поэтому все существующие (committed) записи обязательно попадут в backup. Однако, если поврежден уникальный индекс, то в определенных условиях существует вероятность повторной вставки записи в таблицу с уже существующим (уникальным) значением столбца. Эта ситуация может привести к невосстановимому backup, т. е. процесс restore остановится в момент создания уникального индекса, обнаружив дубликат уникального значения в восстановленных записях. Но такая проблема также не является катастрофической  процесс создания индексов выполняется самым последним, т. е. после того как абсолютно все объекты БД уже восстановлены в базе данных процессом restore. Если вдруг обнаружена проблема неуникальных данных при создании индекса, можно попробовать найти такую запись (и затем удалить лишнюю) запросом
SELECT ID, COUNT(*) FROM TABLE
GROUP BY ID
HAVING (COUNT(*)) > 1
где id столбец, по которому есть несоздаваемый уникальный индекс. После этого можно активировать индексы, которые не были восстановлены (установив RDB$INDICES.RDB$INACTIVE в 0, там, где этот столбец не 0).

Повреждения таблиц

Нормальная база данных  это не набор отдельных таблиц. Таблицы между собой могут быть достаточно сильно взаимосвязаны, вплоть до циклических ссылок. Поэтому даже один и тот же тип и объем повреждения может иметь разные последствия, в зависимости от того, с какой таблицей это произошло. Типичный пример: таблица CLIENTS  справочная, а ORDERS  оперативная. Если пропадет часть записей из ORDERS, то после починки БД будет нормально функционировать. Если же будет повреждена CLIENTS, то после починки в ORDERS будут записи, ссылающиеся на несуществующих клиентов. Таким образом БД вроде бы будет отремонтирована, но логическая целостность данных будет нарушена. Бороться с этой ситуацией никак невозможно, разве что чаще делая backup (поскольку справочники меняются реже, чем оперативные данные). Подобная ситуация (с повреждением master-таблицы) может возникнуть даже в том случае, если все записи пакета master-detail вставляются в одной транзакции, а Forced Write выключен (off)  страницы с записями detail могут быть записаны на диск операционной системой из кэша раньше, чем соответствующие им записи таблицы master. Это не приводит к "невосстановимому backup", но после restore придется или добавлять недостающие master-записи, или удалять "осиротевшие" detail-записи (в зависимости от сложности триггеров, обрабатывающих вставку в master или удаление в detail. Возможно, такие триггеры на время ремонта данных нужно будет отключить).

Также, в подобной ситуации, при restore отремонтированной базы данных могут оказаться неактивными (rdb$indices.rdb$index_inactive = 1) индексы по Foreign Key соответствующих связей master-detail. Активировать их можно после упомянутых вставок или удалений master/detail, путем установки rdb$indices.rdb$index_inactive в 0. О повреждениях системных таблиц см. дальше.
 

Ремонт БД

Для ремонта баз данных рекомендуется использовать именно утилиту командной строки gfix, несмотря на то, что раньше такие операции можно было делать из Server Manager, а сейчас из IBConsole или через Services API.
 
Внимание! Даже если у вас только что рухнула база, и вы хотите починить ее максимально быстро – все равно ВНИМАТЕЛЬНО прочитайте указанные ниже пункты от 1 до 8.
Повреждения баз данных могут быть исправлены как при помощи только gfix, так и одновременно gfix и gbak. Открываем консольное окно (cmd на W2K, или command на Win9x, или терминальное на Linux)

Лично я предпочитаю при операциях backup/restore всегда сохранять вывод в файл ключами -v -y outfile.txt. При обычном выводе на консоль "вывод" может потеряться, и тогда придется процедуру backup/restore повторять. Кроме того, в файле лога можно легко найти список объектов, которые успешно восстановлены.

1. Установите системные переменные, чтобы облегчить себе жизнь и не вводить постоянно для gfix/gbak параметры -user SYSDBA -pass masterkey
SET ISC_USER=SYSDBA
SET ISC_PASSWORD=masterkey

 
Внимание! Не оставляйте эти переменные после починки БД. В этом случае клиент с любой машины может подсоединиться к БД без указания имени пользователя и пароля (например, isql). Если не хотите устанавливать эти переменные, то по тексту дальше дописывайте
gbak .... -user SYSDBA -pass masterke
gfix .... -user SYSDBA -pass masterke

2. При починке работайте с копией базы данных, а не с оригиналом. У вас должен быть эксклюзивный доступ к БД
copy employee.gdb database.gdb
 
Внимание! Копировать БД таким способом можно только если вы абсолютно уверены, что к БД никто не подсоединен. База данных – файл произвольного доступа, а файловое копирование производится поблочно-последовательно. Поэтому если с БД кто-то работает, вы 100% получите "на выходе" испорченный файл БД вместо копии. Существуют редкие сообщения и о том, что при копировании с подключениями может испортиться и оригинальный файл БД, правда природа этого случая неясна.

3. Иногда помогает перед началом починки перевести базу данных в режим shutdown
gfix database.gdb -shut -force 0
 
Внимание! Не забудьте потом вернуть ее в online командой gfix database.gdb -online

4. Проверьте базу данных на повреждения

gfix при проверке БД на наличие повреждений выводит информацию о повреждениях в interbase.log или firebird.log (подробно) и на экран (суммарно). Поэтому чтобы посмотреть на подробное описание повреждений, перед запуском команды ремонта БД следует переименовать interbase/firebird.log (например, firebird1.log, firebird20071010.log и т. д.), чтобы уже хранимая там информация не мешала дополнительному выводу gfix. Когда сервер не обнаружит лог-файл, он его создаст заново.
gfix -v -full database.gdb

Если выдаются ошибки checksum error, то нужно выполнить следующую команду:
gfix -v -ignore database.gdb

5. Если предыдущая команда обнаружила ошибки, то нужно их исправить командой
gfix -mend database.gdb
если выдаются ошибки checksum error, то нужно добавить опцию -ig
gfix -mend -ig database.gdb
 
Внимание! Ключ -mend помечает поврежденные структуры как исключаемые при backup. В результате целостность между таблицами может быть нарушена, и даже если после этого backup пройдет, не факт что базу данных удастся восстановить из backup. В этом случае придется вручную копировать данные при помощи утилит вроде IBPump.

6. Проверим, все ли починилось
gfix -v -full database.gdb

7. Если на этот момент вы все еще видите ошибки, то надо попытаться сделать backup, при этом обязательно нужно отключать сборку мусора (ключ -g)
gbak -b -v -ig -g database.gdb database.gbk
ключ -ig игнорирует ошибки при чтении структур данных, и пытается сохранить в backup все неповрежденные структуры и данные.
 
Внимание! Никогда не указывайте ключ -ig при обычном бэкапе – если в базе есть ошибки, gbak их проигнорирует и вы не узнаете, что база была повреждена. В результате такой бэкап может быть невосстановимым.
Если ваши приложения работают с двухфазным коммитом, то возможно потребуется ключ -limbo для игнорирования зависших 2pc транзакций (limbo).

8. Теперь надо попытаться восстановить базу данных из backup
gbak -c -v database.gbk new.gdb
 
Внимание! Никогда не делайте restore поверх существующей базы данных. В случае ошибки при restore вы лишитесь оригинальной базы данных.

9. Если при restore есть ошибки, то нужно попробовать следующие ключи:
-inactive, если есть проблема с индексами, то с этим ключом база будет восстановлена с деактивированными индексами. Их можно потом активировать (altex index X active) поодиночке.
-one_at_a_time, этот ключ приводит к commit после восстановления каждой таблицы. По крайней мере будет восстановлена хотя бы часть таблиц.
 
Внимание! gbak в InterBase 7.x и выше по умолчанию не проверяет при restore следующие ограничения – not null, check, primary, unique, foregin key. Для восстановления оригинального функционирования gbak процесс restore должен проводиться с ключом -validate. У Firebird наоборот, проверка ограничений включена, поэтому для ее отключения нужно указывать ключ -no_validity. Если до сих пор так и не получилось сделать нормально backup и restore, но доступ к поврежденной базе все-таки есть, можно попробовать утилитой IBPump (или другими) скопировать хотя бы часть (неповрежденную) данных.


Невосстанавливаемый backup

Получить backup, который не пройдет restore ("невосстанавливаемый" или "невосстановимый") вполне возможно по ряду причин и без сбоев сервера или оборудования. Для минимизации потраченного времени на поиск мест, где эти ошибки происходят, всегда нужно делать restore с ключом -v, и еще лучше – с выводом в файл (gbak .... -v -y rest_log.txt). Если с починкой не сможете справиться сами, то тогда как минимум сможете предъявить в news-конференции или службе технического сопровождения точное сообщение об ошибке.

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

Проблемы с индексами

Как известно, восстановление базы данных из бэкапа происходит следующим образом:
  1. gbak дает команду серверу создать новую БД с параметрами из бэкапа,
  2. gbak копирует в новую базу пользовательские метаданные из бэкапа,
  3. gbak копирует в новую базу пользовательские данные из бэкапа,
  4. gbak активирует (создает) все индексы.
Пункт 4, то есть создание индексов, не случайно выполняется после восстановления всех данных. Если бы индексы были активны сразу после пункта 2, то заливка данных в БД была бы очень медленной.Соответственно, раз индексы создаются в самом конце процесса restore, ошибки при их создании не являются фатальными для функционирования БД, разве что без индексов запросы будут работать очень медленно. Во всех версиях InterBase и Firebird (кроме Firebird 2.x) при первой же ошибке создания индекса дальнейшее создание индексов прекращается. В Firebird 2.x ошибка создания индекса сообщается, но создание других индексов продолжается, что позволяет легко идентифицировать неактивные индексы по окончании restore.

В данном случае считать базу "невосстановленной" нельзя. Но понятно, что из-за неактивности некоторых индексов определенные запросы будут работать медленнее, и ссылочная целостность по этим FK проверяться не будет.

Дубликаты в уникальных индексах

Как уже было упомянуто в начале статьи, повреждения индексов обычно незаметны. Более того, повреждения уникальных индексов (ограничения Primary Key, Unique или просто уникальный индекс) могут привести к появлению неуникальных дубликатов записей в таблице. Во время работы это заметно не будет, однако при попытке restore базы данных может быть выдано сообщение о невозможности создать конкретный индекс.Интересно, что пользователи InterBase 7.x/2007, 2009, XE могут вообще не обнаруживать эту проблему, т. к. gbak в InterBase 7.x и выше по умолчанию не проверяет при restore следующие ограничения – not null, check, primary, unique, foregin key. То есть база может пережить много циклов сохранения-восстановления без обнаружения данной проблемы. Данное поведение полезно только в том случае, когда вам нужно восстановить именно невосстанавливаемый с данными проверками бэкап. Для принудительного контроля логической целостности при restore настоятельно рекомендуется добавлять ключ -validate.

У Firebird наоборот, по умолчанию ограничения проверяются, а выключить их проверку можно указав ключ -no_validity. Соответственно, при наличии дубликатов в уникальном индексе нужно эти дубликаты найти, а затем удалить. После чего проверить логическую целостность БД через бэкап-рестор.

Отсутствующие записи по Foreign key

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

Затем можно или записи с этими идентификаторами удалить из таблицы деталей, либо добавить соответствующие записи в таблицу-мастер (первое быстро, но с потерей данных; второе – долго, зато данные остаются). После исправления несоответствий в мастер-детали можно пересоздать (или активировать индекс) Foreign key.

Добавленный столбец NOT NULL

Это самая частая проблема, с которой сталкиваются разработчики. Известно, что IB позволяет добавлять новые столбцы к таблице даже если есть любое количество данных в этой таблице. При этом меняется формат таблицы (запись в системных таблицах), однако существующие записи никаким образом не обновляются. Процесс backup запишет данные как есть, и не обнаружив значения столбца установит флаг соответствующего столбца у записи в NULL. При restore контроль not null не даст записать такие данные в таблицу.
 
Внимание! gbak в InterBase 7.x не проверяет при restore следующие ограничения – not null, check, primary, unique, foregin key. Для восстановления оригинального функционирования gbak процесс restore должен проводиться с ключом -validate.
В Firebird для gbak при restore нужно указывать дополнительный ключ -no_validity. Если такая ошибка при restore является единственной, то можно попытаться сделать следующее:
  1. В базе данных у столбца убрать контроль not null. Сделать backup только метаданных (ключ -m). Если оригинальной базы уже нет, т.е. есть только бэкап, то сделать restore только метаданных, убрать в базе у столбца контроль not null... сравнить побайтово бэкап метаданных, полученный пунктом 1, и бэкап с данными. Сравнивать, естественно, по размеру бэкапа метаданных. Просмотреть обнаруженные отличия hex-редактором, чтобы убедиться, что отличия находятся примерно в месте расположения объявления домена или столбца. Исправить отличия hex-редактором в backup.
  2. Сделать restore только метаданных. Если флаг not null у столбца или домена пропал, значит все хорошо, и можно делать restore всего backup.
Если приведенная процедура кажется вам сложной, или вы не хотите заниматься этим в будущем – перед бэкапом используйте утилиту CheckNull.

Изменение типа столбца

То же самое, что и предыдущий случай. Допустим, у таблицы есть строковый столбец. Его потребовалось заменить на столбец типа integer. IB допускает такую операцию (alter table alter column в IB6 и выше), но не пытается в момент изменения типа столбца преобразовать данные. Если хоть в одной записи в таком столбце случайно оказался символ, который невозможно преобразовать в число, restore не пройдет.

Помните, что исправить ситуацию повторным изменением типа столбца с integer на строку не удастся! В таких случаях обязательно надо перед изменением типа столбца проверять его на последующую читаемость операцией
SELECT CAST(FIELD as INTEGER) FROM TABLE
и просмотром всех записей возвращаемых этим запросом в любом инструменте. Если при просмотре всей таблицы не произошло ошибок вроде "transliteration ... overflow...", то тип столбца можно смело менять. Если же ошибка была, нужно искать "проблемную" запись, и менять ее значение на допустимое для преобразования.То же самое относится и к случаю уменьшения длины строк. Операция alter, допустимая в IB 6 и выше, не даст уменьшить длину строкового столбца, т. к. данные могут оказаться длиннее нового размера столбца. Поэтому более безопасным способом (и для изменения типа столбца) является добавление нового столбца с нужным типом, копирование данных в него (update table set oldfield=newfield), удаление старого столбца и переименование нового в старое имя.

Однако такой способ часто не подходит, если на столбец есть масса ссылок из других объектов БД (например, таблиц), или объем данных очень велик. Изменить длину столбца или его тип в этом случае остается возможным только через системные таблицы. Но опять же, лучше заранее проверить максимальную длину данных (используя udf strlen в любой библиотеке udf, не из ib_udf) – select max(strlen(field)) from table.

Check constraint, не соответствующий хранимым данным

Cмысл проблемы состоит в том, что при restore сначала восстанавливаются метаданные, а потом уже сами данные. Исправить ситуацию можно используя при restore gbak с ключом -n, однако при этом не будут восстановлены ВСЕ check constraints у всех таблиц.

Данная ситуация также может возникнуть, например, если в для проверки столбца используется select. Если база данных была повреждена, и вы сделали gfix -mend, часть данных может пропасть (оказаться поврежденными и не попасть в backup). А раз часть данных пропала, ею могут оказаться как раз те данные, которые проверяли столбец по select. Также, в самых первых версиях InterBase 6.0 была ошибка, при которой check constraints проверяли данные до срабатывания триггеров. Таким образом, забыв о контроле check constraint можно было написать такой триггер, который изменял бы данные в противоречивое с check constraint состояние. Например, check constraint проверяет значение столбца > 0 и < 10, а триггер мог спокойно содержать текст вроде new.field + 100. В результате тоже можно было получить невосстановимый backup. В InterBase 6.5, последних версиях Firebird и Yaffil, эта ошибка исправлена.

Кроме перечисленных случаев для check constraints и computed by хочется отметить еще один. Синтаксис create/alter table допускает создание контроля (или вычисляемого) поля как SELECT из другой таблицы. Т. е. это разрешено в соответствии со стандартом, но может оказаться) крайне неэффективным.

Мне приходилось видеть запрос с sum и группировкой в check constraint. Это означает что при выборке такого столбца указанный запрос будет выполняться каждый раз. В отношении check это означает, что заданный запрос а) будет выполняться при каждом insert или update, б) может привести к невосстановимому backup.

Обычно люди не задумываются, каким образом серверу удается восстановить данные из бэкапа для взаимосвязанных таблиц. Например, если оперативная таблица (накладные) восстанавливается раньше чем справочная таблица (товары). Фокус в том, что FK, который отслеживает целостность между этими двумя таблицами, активируется после восстановления всех данных. А в случае check constraints или computed by речь идет о структурах таблиц, которые сначала создаются, а потом заполняются.

Пример: у таблицы X есть столбец, для которого определен контроль
CHECK(STUFF IN (SELECT STUFF FROM STUFFHOLDER WHERE STUFF_ID=100))
такая конструкция может сделать невосстановимым бэкап дважды:
  1. если структура таблицы X будет восстанавливаться раньше таблицы STUFFHOLDER
  2. если данные таблицы X будут восстанавливаться раньше данных таблицы STUFFHOLDER
Кроме этого, весьма странно устанавливать зависимость метаданных таблицы от конкретных значений данных совсем другой таблицы. В такой ситуации лучше и намного безопаснее создать триггер с указанной проверкой (а кроме того заменить IN на EXISTS. Еще правильнее вообще так не делать, а сделать нормальный Foreing Key на STUFFHOLDER).

Еще одна ошибка, которая связана с check constraint, но не с данными, уже давно исправлена в Firebird и Yaffil. Это использование русских букв в default для строковых столбцов. При restore такая БД выдаст сообщение "arithmetic exception, numeric overflow, or string truncation" еще на этапе восстановления метаданных. При наличии оригинальной базы данных необходимо удалить такой default, и сделать backup повторно. При отсутствии БД следует попытаться использовать ключ gbak -n.

Процедуры и триггеры

Здесь ситуация с невосстановимым backup может произойти при модификации (alter) процедур, которые вызываются другими триггерами или процедурами. Если меняется только тело процедуры, то проблем не будет. А если меняется количество или типы параметров процедуры, то в этом случае при restore (да и при alter вызывающих процедур) будет выдаваться сообщение о том, что список параметров процедуры не соответствует используемому.

Проблема там же, где и всегда – при реконструировании метаданных объекты строятся в памяти один за другим, образуя целые "гирлянды" из взаимосвязанных объектов. При этом код этих объектов (текст процедур или триггеров) не перекомпилируется. Но список параметров процедур хранится отдельно, в rdb$procedure_parameters, поэтому несоответствие используемомого и хранимого наборов становится камнем преткновения для restore. Борьба с этой ситуацией возможна фактически только при наличии оригинальной БД. Сейчас можно удалить или изменить процедуру, которая вызывает другую с измененным количеством параметров, однако в предыдущих версиях InterBase и Firebird это не было возможным. Единственный способ исправить проблему приведен в документе. Однако, с триггерами, вызывающими такие процедуры, ситуация похуже, потому что сервер не даст возможности обновить blr триггера. Поэтому остаются только "хакерские" приемы, вроде создания "пустой" процедуры с нужным количеством и типами параметров, и изменению blr триггера при помощи hex-editor.

В общем, к подобным изменениям нужно относиться более тщательно, используя инструменты, которые показывают зависимости между объектами (IBExpert), и комментируя вызовы процедур перед изменением количества или типов их параметров.

Повреждения системных таблиц

Разумеется, при повреждении системных таблиц информация о некоторых объектах будет потеряна, и backup не сможет сохранить данные этих объектов. Однако, если сами данные в системных таблицах целы, а индексы системных таблиц повреждены, backup может неверно сохранить информацию об объектах.

Происходит это потому, что backup выполняет запросы к системным таблицам, а эти запросы могут использовать индексы. Если индекс поврежден, то запрос может вернуть неверную информацию (меньше записей, чем их есть на самом деле). Таким образом или часть объектов просто не попадет в backup, или при restore будут серьезные проблемы из-за несоответствия информации в связанных между собой системных таблицах.

Поэтому, если вы в interbase.log наблюдаете информацию о поврежденных индексах на системных таблицах, то делать backup надо только после удаления таких индексов. Поврежденные индексы можно особо не выискивать, а удалить все целиком в rdb$indices, у которых rdb$relation_name начинается с префикса RDB$. Все равно индексы по системным таблицам относятся к системным таблицам, и создаются gbak при restore автоматически, независимо от содержимого backup.

Есть еще один случай повреждения базы данных, который относится к правам на объекты (таблицы, процедуры и т. п.). Проявляется это повреждение как ошибка в процессе restore с сообщением об отсутствии объекта, на который существуют назначенные права (в самом конце процесса restore). Как следует из кода restore.e, данная проверка является избыточной, т. к. если объекта нет, то и права на него восстанавливать не надо. Однако, код restore.e в таком случае выдает не предупреждение, а сообщение об ошибке, в результате чего процесс restore не доходит до завершения.

В конкретном случае были попытки определить несоответствие в rdb$user_privileges и остальных таблицах (rdb$relations и т. п.), однако таковых найдено не было. Ситуацию удалось решить компиляцией специального варианта restore.e (gbak.exe), в котором при отсутствии объекта, на который выдаются права, просто ничего не делается (не выдается никаких сообщений). Остается надеяться, что в новых версиях IB/FB/YA эта ситуация будет исправлена.

Пока что для обнаружения подобных проблем можно воспользоваться следующим способом – при правильном backup/restore всегда оставляют оригинальную БД (см. невосстанавливаемый backup). Для проверки существования в восстановленной базе данных всех объектов можно извлечь скрипты из обоих БД (например isql-ом), и сравнить их утилитой Database Comparer (info, download). Если отличий найдено не будет, значит структуры обоих БД идентичны. Если нет – найти поврежденный индекс и саму системную таблицу очень легко, зная в каких системных таблицах какие объекты хранятся (Language Reference.pdf).

Проблемы оборудования

Проблемы "железа" тоже могут сделать бэкап невосстанавливаемым. Я сам столкнулся со случаем, когда пользователь сообщил о том, что при попытке restore ошибки выдаются в разных местах. Было сделано предположение о дефективном HDD или RAM. Последнее предположение подтвердилось.В таких случаях хорошо, если проблемы оборудования проявили себя на этапе restore, а не backup. Если бы память (RAM) начала сбоить в момент backup, никто бы не узнал о проблемах вплоть до момента restore, и наверняка файл backup был бы поврежден так, что его нельзя было бы восстановить никаким образом. Причем никакими средствами вроде "контрольных сумм backup" предотвратить эту ситуацию невозможно, т. к. в файл данные пишутся именно операционной системой из памяти.

Поэтому позволю себе дать несколько банальных советов: не используйте модули RAM разных производителей в одном компьютере
старайтесь для сервера выбирать motherboard и память, поддерживающие ECC.
 

Дополнительные способы ремонта БД

Иногда возникают ситуации, которые не позволяют сделать backup даже после всех усилий применения gfix. Например, при бэкапе одной БД gbak выдал сообщение "Blob xxx corrupted". Если вы прочитаете статью по процессу gfix в конце этой статьи, то там написано, что такие ошибки чинятся gfix с ключом -mend. Однако это все равно не помогло, сообщение об ошибке не пропадало, и сделать backup было невозможно. Поэтому были проделаны следующие шаги:
  1. В инструменте, который не помещает записи в буфер (не кэширует. Можно написать простую программу с использованием компонента IBSQL из IBX. Главное, чтобы при больших объемах считываемые данные не попадали в кэш программы, иначе к концу считывания пары миллионов записей программа будет работать ужасно медленно) были просмотрены все записи через PgDn. При этом записи считываются с диска, но блобы не выбираются (можно написать select field1, field2, field... from table, исключая столбцы blob, если просмотр данных все-таки идет в гриде). Обнаружено, что таблица читается целиком. Значит, действительно поврежден блоб.
  2. Для нахождения поврежденного блоба сканирующая программка была дополнена операцией чтения блоба, простым присвоением переменной string (asString). Перед чтением блоба ключевое поле записи выдавалось на экран.
  3. В момент ошибки ключевое поле записи запомнили, после чего сделали
UPDATE TABLE
SET BLB=NULL
WHERE PK_FIELD = x
где вместо x подставили значение ключевого поля. Поскольку значение blob было испорчено, главным было обеспечить backup остальных записей и их blob.

Backup после этой операции прошел успешно.

То же самое можно проделывать в отношении "дефектных" записей (или их версий), при чтении которых выдается сообщение об ошибке. Основная трудность состоит в том, что физически на диске записи неотсортированы, и придется несколько раз выполнять запросы, выбирающие записи с разных "сторон" таблицы, чтобы найти примерно запись с какими данными (первичным ключом) не может быть прочитана. Пример кода, который производит копирование записей из "дефектной" таблицы в другую БД.

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

Внутри val.c есть кусочек документации, описывающий что и как делает gfix. Вот перевод этого куска. (Ни один другой файл исходного текста не содержит подобной "документации").
 

Database Validation and Repair

Deej Bredenberg
March 16, 1994
Updated: 1996-Dec-11 David Schnepper

I. Терминология

Приводимые термины помогут понять этот документ:
  • record fragment (фрагмент записи): наименьший распознаваемый кусочек записи. Несколько фрагментов могут быть связаны и формировать отдельную версию записи.
  • record version (версия записи): Версия записи, созданная в результате выполнения оператора INSERT, UPDATE или DELETE в какой либо транзакции (обратите внимание, что при удалении создается новая версия записи, которая представляет собой так называемый "deleted stub").
  • record chain (цепочка записей): Связанный список версий записи, который представляет собой целостную "запись".
  • slot (слот): Номер строки, где находится запись на странице. Массив переменной длины на каждой странице данных хранит смещения на записи, находящиеся на этой странице. Слот является индексом этого массива. За более подробной информацией по структуре страницы данных обращайтесь к моему документу по ядру Interbase (документ отсутствует).

II. Опции командной строки

Вот все ключи командной строки, которые используются при починке базы gfix-ом:
ключ константа dbp  
-validate isc_dpb_verify Выполняет проверку и починку БД. Все другие ключи модифицируют действие этого ключа.
-full isc_dpb_records Проверяет все записи. Без этого ключа проверяются только структуры страниц.
-mend isc_dpb_repair Пытается починить базу данных, чтобы она хотя бы могла быть читаема. Не гарантирует восстановления испорченных данных.
-no_update isc_dpb_no_update Указывает, чтобы осиротевшие страницы не освобождались, а занятые не помечались как используемые, если обнаружится что они на самом деле свободны. Не совсем корректное название ключа, потому что при его употреблении с -mend база будет чиниться, а если -mend не указан и указано -no_update, никаких изменений в БД сделано не будет.
-ignore isc_dpb_ignore Выключает подсчет контрольных сумм при выборке страниц. Проверка все равно будет сообщать контрольные суммы. Рекомендуется к использованию.
Замечание: ODS 8.0 не имеет контрольных сумм на страницах. ODS 9.0 вообще не имеет контрольных сумм.

III. Как это работает

Проверка БД работает только при эксклюзивном доступе к базе данных, чтобы в момент починки не было никаких посторонних изменений. При подключении к БД происходит попытка поставить эксклюзивную блокировку на файл базы данных. Если обнаружены другие коннекты к БД, выдается сообщение: "Lock timeout during wait transaction -- Object "database_filename.gdb" is in use"

ЗАМЕЧАНИЕ: Обычно, когда процесс получает эксклюзивный доступ к БД, все активные транзакции помечаются как dead в Transaction Inventory Pages. Это поведение выключено при проверке БД.

IV. Фазы проверки

Существует две фазы проверки. В первой сканируется вся база данных. В этот момент номера всех страниц помещаются в битовую маску, для последующего использования в фазе сборки мусора

A. Просмотр страниц

В первой фазе любая выбираемая страница проходит проверки:
1. Контроль типа страницы
Каждая страница проверяется на ожидаемый тип страницы. Если в заголовке страницы указан неверный тип страницы (с точки зрения распределения страниц), то выдается сообщение: "Page xxx wrong type (expected xxx encountered xxx)" Это может сигнализировать о
  1. проблеме в базе данных
  2. ошибке в механизме распределения страниц IB, что привело к переписыванию одной страницы другой
  3. том, что страница, которая была распределена, не была записана на диск (чаще всего в этом случае тип страницы = 0). Это сообщение не говорит о том, как называется соответствующих тип страницы, поэтому здесь приведена выдержка из ods.h:
#define pag_undefined 0 /* не определено */
#define pag_header 1 /* страница заголовка базы данных */
#define pag_pages 2 /* Page Inventory Page - страница списка страниц */
#define pag_transactions 3 /* Transaction Iinventory Page - страница списка состояния транзакций*/
#define pag_pointer 4 /* страница указателей */
#define pag_data 5 /* страница данных */
#define pag_root 6 /* корневая страница индекса */
#define pag_index 7 /* страница индекса (B-tree) */
#define pag_blob 8 /* страница данных Blob */
#define pag_ids 9 /* страница значений генераторов */
#define pag_log 10 /* Write ahead log: только для 4.0 */
2. Контрольные суммы
Если указан ключ -ignore, то контрольная сумма считается при проверке, но не ядром. Если контрольная сумма не совпадает, то будет выдана ошибка:"Checksum error on page xxx" Это не влияет на проверку, и страница будет продолжать проверяться. Если указан ключ -mend, то страница в конце проверки будет записана на диск, и ее контрольная сумма будет автоматически пересчитана.
ЗАМЕЧАНИЕ: Только 4.0 для Windows и Netware обрабатывает контрольные суммы.
3. Повторный просмотр
Мы проверяем каждую выбранную страницу в битовой карте выбранных страниц, чтобы просматривать только новые страницы. Если номер повторяется, то будет выдано сообщение: "Page xxx doubly allocated" Это означает что на страницу одного и того же типа дважды ссылаются из разных мест. Страницы данных не проверяются этим механизмом, поскольку они проверяются намного чаще при просмотре фрагментов и цепочек записей.

B. Сборка мусора

В этой фазе страницы Page Inventory (PIP) проверяются на упоминание в битовой карте просмотренных страниц. Этим детектируются два типа ошибок:
1. Осиротевшие (Orphan) страницы
Если в PIP упомянута страница, которая еще не прошла проверку, то будет выдано сообщение "Page xxx is an orphan". Если не был указан ключ -no_update, то страница будет помечена в PIP как свободная.
2. Некорректно освобожденные страницы
Если страница помечена как свободная в PIP, но проверялась при первой фазе проверки, будет выдано сообщение:"Page xxx is use but marked free" (sic) (KDV: ни разу не видел такого сообщения. А вот page xxx is orphan – хоть отбавляй, особенно при крахе ОС в тот момент, когда производится заливка данных в БД). Если не был указан ключ -no_update, то такая страница будет помечена в PIP как используемая.
ЗАМЕЧАНИЕ: Если в фазе проверки были обнаружены ошибки, то PIP изменяться не будут. Потому что нет шансов проверить все страницы, если обнаружены поврежденные структуры.

V. Фаза просмотра

A. Выбор страниц

Для проверки того, что все страницы были считаны, следующие страницы проходят следующие проверки:
1. Заголовок базы данных (нулевая страница).
2. Page Inventory pages.
3. Transaction Inventory pages. Если невозможно просмотреть системную таблицу RDB$PAGES, или эта таблица не содержит ни одного номера страницы TIP, выдается сообщение: "Transaction inventory pages lost". Если какая то страница пропала из списка, указанного в RDB$PAGE_SEQUENCE, будет выдано следующее сообщение: "Transaction inventory page lost, sequence xxx". Если указан ключ -mend, то распределяется новая страница TIP, и корректно записывается в RDB$PAGES. Все транзакции, которые находились на потерянной таким образом странице, считаются завершенными по committ. Если страница TIP не указывает на следующую в последовательности страницу, то выдается сообщение: "Transaction inventory pages confused, sequence xxx" 5. Страницы генераторов, как указано в RDB$PAGES.

B. Проверка таблиц

Проверяются все таблицы в базе данных. Для каждой таблицы выбираются все индексы, и все страницы указателей и данных (см. дальше). Но вначале, сканируются метаданные в RDB$RELATIONS для определения формата таблицы. Если эта информация утеряна или повреждена, таблица не может быть поверена. Если любые ошибки бнаружены в момент проверки, выдается сообщение: "bugcheck during scan of table xxx ()" После этого любая дальнейшая проверка таблицы прекращается.
ЗАМЕЧАНИЕ: Для views производится только проверка метаданных.

C. Проверка индексов

До версии 4.5 (NevaStone) индексы проверялись перед страницами данных. В 4.5 проверка индексовы была перенесена после проверки страниц данных. См. дальше раздел "Проверка индексов". (KDV: версию 4.5 практически никто не видел. Выпущена она была для какой то одной операционной системы, и поставлялась только клиентам, использующим 4.1. В продажу не поступала)

D. Страницы указателей

Проверяются все страницы указателей для таблицы. При их проходе проверяются все дочерние страницы (см. дальше). Если страница указателей не может быть найдена, выдается сообщение: "Pointer page (sequence xxx) lost". Если страница указателей оказалась не той таблицы, которую мы проверяем, или она не помечена в правильной последовательности, выдается сообщение: "Pointer page xxx is inconsistent". Для каждой страницы указателей, которая не указывает на следующую страницу указателей в соответствии с столбцом RDB$PAGE_SEQUENCE в RDB$PAGES, выдается ошибка: "Pointer page (sequence xxx) inconsistent".

E. Страницы данных

Проверяется каждая страница данных, на которую указывает страница указателей. Если найдены повреждения на уровне страницы, и указан ключ -mend, то страница удаляется со страницы указателей. Это приводит к потере данных, которые располагались на этой странице данных. Если страница данных повреждена на уровне страницы, и не помечена как часть проверяемой таблицы, или не помечена в правильной последовательности, выдается сообщение: "Data page xxx (sequence xxx) is confused".

F. Проверка слотов

Проверяется каждый слот на странице, и на ней обновляется количество хранимых записей. Если слот не нулевой, то выбирается фрагмент записи по указанному смещению. Если запись начинается перед концом массива слотов, или продолжается за пределами страницы, выдается сообщение: "Data page xxx (sequence xxx), line xxx is bad", где "line" означает номер слота.
ЗАМЕЧАНИЕ: Если обнаруживается такое условие, страница данных считается поврежденной на уровне страницы, и в результате будет удалена со страницы указателей, если указан ключ -mend.

G. Проверка записей

Проверяется запись в каждом слоте, независимо от указания ключа -full. Фрагмент записи может быть опознан как
1. Обратная версия
Если фрагмент помечен как "обратная" версия, то он пропускается. Он будет выбран как часть записи.
2. Поврежденный
Если обнаруживается, что фрагмент поврежден любым образом, и указан ключ -mend, то в заголовке запись помечается как "поврежденная".
3. Помеченный как поврежденный
Если фрагмент уже помечен как поврежденный в результате предыдущей проверки, то выдается сообщение: "Record xxx is marked as damaged", где xxx является номером записи.
4. От неверной транзакции
Если запись имеет номер транзакции больше, чем номер самой последней транзакции в базе данных, выдается сообщение: "Record xxx has bad transaction xxx".

H. Проверка записей

Если указан ключ -full, и фрагмент является первым фрагментом логической записи, то запись в соответствующем номере слота выбирается целиком. Это приводит к выборке всех версий, и всех фрагментов для каждой версии. Другими словами, запись извлекается целиком.
1. Обратные версии
Если есть обратные версии, то они в этот момент проверяются. Если обратная версия находится на другой странице, то страница выбирается, но не проверяется пока она не будет проверена отдельно (проверкой страниц). Если номер слота обратной версии больше количества записей на странице, или нет записи в таком номере слота, или это blob, или это фрагмент записи, или фрагмент поврежден, выдается сообщение: "Chain for record xxx is broken".
2. Неполные
Если заголовок записи имеет пометку "неполный", это означает что для выборки записи нужно обратиться к другим фрагментам – запись оказалась слишком большой, чтобы поместиться в один слот. Для фрагментированных записей все фрагменты выбираются для всех версий записей. Если любой из фрагментов не обнаруживается в ожидаемом месте, или имеет неверную длину, выдается сообщение: "Fragmented record xxx is corrupt". Как только запись выбрана целиком, проверяется длина формата, в соответствии с ожидаемым в RDB$FORMATS (номер формата хранится в заголовке записи, представляя точную структуру таблицы на тот момент, когда запись была сохранена на диск). Если длина реконструированной записи не соответствует длине, получаемой из формата, выдается сообщение: "Record xxx is wrong length". Для версий записей, которые представляют собой результат действия оператора update, данная проверка не производится.

I. Проверка блобов

Если слот на странице данных указывает на запись blob, то blob выбирается (даже без -full). При этом возникает ряд вариантов, в соответствии с различными уровнями blob (см. документацию "Особенности ядра"). (KDV: документация с названием Engine Internals (Особенности ядра) не опубликована до сих пор). Действия в зависимости от уровня
----- -----------------------------------------------------------------
0 Данные blob находятся тут же, на странице данных. Дополнительные проверки не производятся.
1 Все страницы, на которые указывает запись blob, выбираются и проверяются.
2 Все страницы, на которые указывают страницы указателей blob, выбираются и проверяются..
3 Страница blob является указателем на страницы blob. Все дочерние страницы выбираются и проверяются. Для каждой найденной страницы blob производятся проверки. Если страница не указывает обратно на страницу-источник, то выдается сообщение: "Warning: blob xxx appears inconsistent" где xxx это номер записи blob. Если любая из страниц blob не отмечена в последовательности, как это ожидается, выдается сообщение: "Blob xxx is corrupt" Tip: сообщение для такой же ошибки, но на уровнях 2 и 3, немного отличается: "Blob xxx corrupt". Если потеряна любая из страниц blob в последовательности, выдается сообщение: "Blob xxx is truncated". Если обнаруживается, что выбираемый blob поврежден одной из вышеуказанных причин, и указан ключ -mend, то запись blob помечается как поврежденная. (KDV: иногда маркировка как "damaged" не помогает. Т. е. сообщение blob xxx corrupt выдается при backup независимо от того, как и с какими ключами производилась починка БД gfix. См. пример ручного исправления ситуации в начале статьи).

J. Проверка индексов

В версии 4.5 (NevaStone) проверка индексов выполняется после завершения проверки страниц данных. Проверяются индексы для конкретной таблицы. Если корневая страница индексов не обнаружена, выдается сообщение: "Missing index root page" и индексы не проверяется. Иначе считывается корневая страница все индексы, упомянутые на этой странице, проверяются. Для каждого индекса, страницы btree выбираются сверху вниз, слева направо. Базовая проверка нелистовых страниц проверяет, указывает ли каждый узел на другую страницу индекса. Если указано -full, то проверяется целостность страниц нижнего уровня в том, что они указывают на родительские страницы. На листовых страницах указатели на страницы индекс не проверяются. Ключи проверяются на правильный порядок следования (по возрастанию или убыванию). Если проверяемая страница не относится к проверяемой таблице или индексу, выдается сообщение: "Index xxx is corrupt at page xxx". Если есть осиротевшие дочерние страницы, т. е. дочерние страницы, на которые не указывает родительская страница, то обновляется btr_sibling дочерней страницы и выводится сообщение об ошибке: "Index xxx has orphan child page at page xxx". Если страница не содержит число ключей, которые мы ожидаем по подсчитанной длине, то выдаем сообщение: "Index xxx is corrupt on page xxx". При проходе листовых страниц, мы сохраняем битмап всех номеров записей, видимых в индексе. По окончании прохода по индексу мы сравниваем полученный битмап с битмапом записей в таблице (полученном при проходе страниц данных и проверке записей). Если эти битовые маски не эквивалентны, значит мы имеем дело с поврежденным индексом, и выдаем следующее сообщение: "Index %d is corrupt (missing entries)". Мы НЕ проверяем каждую версию каждой записи на существование соответствующего ключа в индексе. Также мы не проверяем что конкретный ключ индекса соответствует определенной записи или версии.

K. Проверка таблиц

Мы считаем количество обратных версий, видимых при просмотре pointer pages, и отдельно считаем количество обратных версий при просмотре цепочек записей. Если эти числа не совпадают, значит или появились "осиротевшие" обратные версии, или двусвязанные списки. В этом случае выводится сообщение: "Relation has %ld orphan backversions (%ld in use)". В настоящее время мы не исправляем такую ситуацию, а только сообщаем о ней. Занятое "осиротевшими" обратными версиями пространство может быть возвращено путем backup/restore. Для двусторонних цепочек SWEEP должен удалять все обратные версии.

VI. Дополнительные замечания

A. Поврежденные записи

Если при проверке обнаруживается повреждение любого фрагмента записи, то заголовок такой записи помечается как "damaged" (поврежденная). Насколько я могу видеть, это не имеет влияния на ядро. Записи помеченные таким образом будут и дальше выбираться ядром. А вот будет ли такая запись выбираться при backup – это вопрос. Если идет обращение к поврежденной записи, то выводится следующее сообщение: "Record xxx is marked as damaged" Обратите внимание, что когда обнаруживается поврежденная запись, это сообщение не выводится. Запись просто помечается как поврежденная. И только после того, как будет повторное обращение к такой записи, будет выведено это сообщение. Поэтому я утверждаю, что пока не будет сделана полная проверка БД, вы можете не увидеть такого сообщения. А вот после полной проверки БД такое сообщение будет появляться даже тогда, когда gfix запускается без ключа -full.

B. Поврежденные Blob

Записи Blob, помеченные как поврежденные, не могут быть открыты и будут удалены с диска. Это означает, что даже при backup помеченные как поврежденные структуры Blob не будут выбраны и сохранены в backup (почему сделано для blob и записей по разному, я не знаю. Может быть это показалось слишком сложным – извлекать поврежденный blob).
 

Заключение

Третий закон Чизхолма: Любые предложения люди понимают иначе, чем тот кто их вносит.
Следствие 1: Даже если ваше объяснение настолько ясно, что исключает всякое ложное толкование, все равно найдется человек, который поймет вас неправильно.

Спасибо за комментарии и поправки к статье:

Олегу Иванову, Альберту Губайдуллину, Алексею Емельянову, Игорю Захребеткову, Александру Невскому, Дмитрию Еманову

Литература

  1. Interbase Operations Guide Diagnosing and Repairing InterBase Database Corruption, Paul Beach
  2. Невосстановимый Backup или GBAK и уверенность в завтрашнем дне, А. Невский.
  3. CVS/interbase/jrd/val.c

Дополнительно читать

  1. А. Н. Ковязин, С. М. Востриков, "Мир InterBase", изд. Кудиц-Образ, 2002 г. 

Ссылки

Подпишитесь на новости Firebird в России

Подписаться