Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
2004 г.

Delete from mytable; Commit:..
или Восстановление данных в СУБД Interbase

Вадим Павлов, Курганский Penguin

Случилось страшное, кровь матери пролил сын обезумевший.

  (c)Кто-то древний

Предисловие

Данная статья написана на основе статей: "The Interbase On - Disk Structure" by Ann Harrison ; "Structure of a Data Page" by Paul Beach, а так же включает в себя мой горький опыт в удалении важных данных, а потом в их успешном восстановлении. Я никоим образом не собираюсь нарушать права ни выше перечисленных авторов, ни компаний, чьи зарегистрированные торговые знаки перечисленные в данной статье.

Эта статья была написана Вадимом Павловым в 2004г. с соответствующим наложением авторских прав. Вы можете опубликовать её по своему усмотрению, единственным условием является опубликование данного абзаца.

По любым вопросам, касающимся данной статьи можно связаться с автором по адресу: pvm _ job(at)mail.ru, либо пообщаться на форуме, в чате сайта penguin.photon.ru

Концептуализация

Итак, всё что можно было сделать плохого мы уже сами своими собственными руками сделали. Но не стоит сильно отчаиваться, в некоторых случаях можно помочь беде.

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

Поиск в интернет, о возможности восстановления данных, не дал нам никаких результатов, поэтому будем делать всё сами.

Мы в данной статье будем рассматривать возможность восстановления данных в СУБД Interbase 6. x , Firebird 1. x для Windows (для других операционных систем возможны небольшие отличия в дисковой структуре).

Сначала немного формализуем задачу.

Что мы имеем:

1. Реляционную базу данных с несколькими тысячами записей, которые необходимо восстановить.

2. Устойчивое желание восстановить базу и немного терпения.

Как вам, наверное, уже известно, сразу после удаления какой либо информации из базы данных Interbase , файлы базы не уменьшаются, значит, вся информация осталась, а были только удалены ссылки на место хранения необходимой нам информации (записей).

После удаления информации о местоположении записей оказывается так, что утерянная информация находится в файле вперемешку с <мусором>. Тем более что, каждой записи может содержаться несколько версий.

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

Не нужно останавливать офис на неопределенный срок, достаточно просто скопировать базу поближе к себе и приступить к работе.

Для удобства можно воспользоваться утилитой IBSurgeon Viewer (http://www.ib-aid.com/), а также любым клиентом sql для Interbase , вполне подойдет ibconsole , wisql , isql и т.д. А так же нам очень помогут статьи The InterBase On - Disk Structure By Ann Harrison , Borland Developers Conference San Diego 2000, Structure of a Data Page By Paul Beach .

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

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

Итак, приступим.

Дисковая структура базы данных Interbase

Для понимания процесса восстановления и возможности такового изложим немного теории.

База данных состоит из одного или нескольких файлов. Файл базы данных, в свою очередь, состоит из набора <страниц> (далее просто страниц) фиксированной длины, размер которых указывается при создании базы данных, каждый файл базы данных содержит в себе страницы одинакового размера. Стандартной является страница 4096 байтов. Достоверность данного утверждения можно проверить, поделив размер файла на размер страницы. У Вас получится количество страниц содержащихся в базе. Для удобства работы с каждой страницей в отдельности можно разрезать данный файл на множество файлов содержащих в себе только одну станицу, либо воспользоваться программой IBSurgeon Viewer .

В Interbase используется несколько видов страниц:

•  Страница-заголовок (Header page)

•  Страница указателей (Pointer page)

•  Страница учета транзакций (Transaction inventory page)

•  Страница распределения места ( Space inventory page )

•  Страница генераторов ( Generator page )

•  Страница основного индекса ( Index root page )

•  Станица индексов (Index page)

•  Станица данных (Data page)

Типы страниц определены в файле ods . h таким образом:

/* Page types */

#define pag_undefined 0

#define pag_header 1 /* Database header page */

#define pag_pages 2 /* Page inventory page */

#define pag_transactions 3 /* Transaction inventory page */

#define pag_pointer 4 /* Pointer page */

#define pag_data 5 /* Data page */

#define pag_root 6 /* Index root page */

#define pag_index 7 /* Index (B-tree) page */

#define pag_blob 8 /* Blob data page */

#define pag_ids 9 /* Gen-ids */

#define pag_log 10 /* Write ahead log information */

#define pag_max 10 /* Max page type */

Страница заголовок (Header page) это первый блок в первом файле в базе данных. Когда Interbase присоединяется к базе данных он считывает первый килобайт файла. Заголовок содержит в себе критичную информацию о базе данных. Она включает в себя номер версии дисковой структуры (On Disk Structure - ODS) и размер страницы. После того, как сервер Interbase установил, что версия дисковой структуры, которая используется в файле им поддерживается, он перечитывает страницу-заголовок, используя правильный размер страницы и узнает необходимую информацию, такую как имена и диапазон страниц, содержащихся во вторичных файлах базы данных, следующую доступную транзакцию и последнюю, интересующую нас, транзакцию.

На последующем шаге ищется ядро системных таблиц и строится внутреннее представление базы данных.

Страница указателей ( Pointer page )

Страница-заголовок содержит информацию о месте положения первой страницы указателей для таблицы rdb$pages . Системная таблица rdb$pages позволяет interbase найти критичные страницы, включающие страницы данных для таблиц. Система читает первую страницу указателей и использует её для поиска первой страницы данных rdb$pages . Из неё затем можно найти страницы данных и индексов других системных таблиц.

Таблица 1.

Записи системной таблицы rdb$pages

RDB$
PAGE_NUMBER

RDB$
RELATION_ID

RDB$
PAGE_SEQUENCE

RDB$
PAGE_TYPE

3

0

0

4

4

0

0

6

6

1

0

4

7

1

0

6

8

2

0

4

9

2

0

6

129

0

0

9

140

0

0

3

169

0

10

3

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

Страница учета транзакций (Transaction inventory page).

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

Чтобы определить состояние транзакции Interbase использует номер транзакции, как индекс в массиве транзакций и просматривает состояние соответствующих битов. Алгоритм более сложный, так как зависит от заголовка страницы. В случае если одна транзакция проверяет состояние другой транзакции и обнаруживает, что она обозначена как активная, а фактически является <мертвой> - (<смерть>транзакции проверяется по средством проверки таблицы блокировки) - то она меняет состояние <мертвой> транзакции с активной на откаченную.

Страница распределения места ( Space inventory page )

Последней из страниц <хранителей очага> является страница распределения места. Страница распределения места показывает какие страницы являются распределенными (<зарезервированными>) и если она зарезервирована, то свободна ли она и насколько.

Страница распределения места находится сразу за страницей-заголовком. Она подобно всем страницам имеет заголовок, в котором определен тип страницы. Остальная часть страницы - массив 1-битных кластеров, который соответствует страницам в базе данных. Каждая страница вне зависимости от типа включена в страницу распределения пространства (за исключением страницы-заголовка). Заголовок страницы распределения места не включает в себя указатель на следующую страницу распределения места, так же эти страницы не перечислены в таблице rdb$pages . Они расположены на определенных интервалах. Таким образом, Interbase вычисляет расположение следующей страницы в зависимости от размера страницы базы данных и длины заголовка.

Когда страница добавляется в таблицу или индекс, Interbase меняет её состояние в странице распределения места. Ошибка "orphan page" возникает, когда сервер находится в середине процесса распределения новой страницы. Запись в страницу распределения места сделана, указанная страница выделена, но новая страница не была записана и осталась как <сирота>.

Страница генераторов ( Generator page )

Страница генераторов последняя из <простых> страниц. Страница генераторов состоит из заголовка и четырехбайтовых записей, которые представляют состояние генераторов. Индексом в этом массиве служит порядковый номер генератора. Таблица rdb$pages содержит в себе записи о страницах генераторов.

Страница основного индекса ( Index root page )

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

Страница индекса ( index page )

Заголовок страницы индекса включает тип страницы и номер страницы следующей на данном уровне.

Индексами называется древовидная структура. На странице основного индекса указывается верхняя страница индекса. Она содержит записи, указывающие на точки следующих уровней индексных страниц.

Страница данных (Data page)

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

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

Эти структуры мы рассмотрим далее более подробно.

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

Индекс содержит смещение, поэтому запись может перемещаться по странице без воздействия на её основной идентификатор - её db_key. Когда Interbase обнаруживает, что страница стала фрагментированной в следствии того что некоторые записи были удалены, то он сдвигает все оставшиеся данные вместе, делая одно большое пространство вместо небольших маленьких фрагментов. Это обычные действия базы данных.

Как же выглядит строка таблицы на этом уровне? Во-первых, идет фиксированной длины заголовок, который включает в себя номер транзакции, которая создала эту версию записи и формат версии для этой записи. Если существует старая версия записи, то заголовок также содержит указатель на неё. В заголовке также указан тип записи: обычная запись, фрагментированная запись, фрагмент или поле blob . Маленькие blob -поля часто расположены на странице вместе с записью, к которой они относятся. Если запись фрагментирована, то в заголовке указывается на фрагмент.

Последняя часть служебных данных в записи - добавление к записи переменной длины битов (кратной 8), определяющие значения NULL в соответствующих полях.

Столбцы таблицы расположены в соответствии со значением rdb$field_id таблицы rdb$relation_fields, которая описывает их. Если порядок, определенный значением поля rdb$position отличается, то высокоуровневый механизм приводит их в соответствующий порядок. Высокоуровневая функция также смотрит на версию формата записи и использует ее для нахождения соответствующего формата в rdb$formats. Все записи будут переведены в наиболее подходящий формат во время перемещения из страницы в кэш записей. До тех пор, пока запись не будет изменена, она не будет перезаписана, даже если её формат устарел.

Если запись является сохраненной ( back version ), её основная версия будет содержать флаг, который показывает какая из них является изменением. Изменения - это набор различий, которые могут быть применены к основной записи для создания её сохраненной ( back ) версии. Формат изменения очень похож на кодирование переменной длины, с байтами указывающим количество заменяемых символов и количество сохраняемых символов.

Записи не фрагментируются до тех пор, пока сжатый размер данных меньше размера страницы. Когда запись модифицируется её сжатый размер может увеличиваться, до тех пор, пока не останется места на странице, в данном случае она будет фрагментироваться. Фрагментация значительно влияет на производительность, так как чтение фрагментированной записи требует выборку, как минимум двух страниц. Запись фрагментированных строк может требовать четыре или более страниц для использования стратегии <точной записи>, которая требует, чтобы изменения на странице были записаны в соответствующем порядке.

Страница BLOB ( Data page )

Страницы, полностью занятые двоичными данными ( BLOB ), можно при определённых условиях выделить в отдельный тип страниц БД, которые не отражаются на странице указателей.

Также страница двоичных данных может быть страницей указателей на другие страницы двоичных данных. Для каждого созданного поля BLOB создаётся запись, содержащая информацию о расположении данных поля и данные о содержимом, которые могут быть полезны при чтении. Механизм хранения таких полей определяется их размером и бывает трёх типов (0,1,2).

•  Механизм 0 . Поле умещается на одной странице БД вместе с записью.

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

•  Механизм 2. Когда места на начальной странице не хватает даже для того, чтобы записать туда массив указателей, InterBase создаст страницу (страницы) указателей на страницы с полем BLOB.

Восстановление информации

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

Подробный формат страницы данных и записи

Рассмотрим формат страницы данных и записи более подробно.

Выборочно информацию возьмем из файла ods . h ( on disk structure ) из поставки Interbase ( firebird ) и опишем ее более подробно.

/* Page types */

#define pag_data 5 /* Data page */

Указывается, что страница данных идентифицируется номером 5.

#ifdef _CRAY

#define MIN_PAGE_SIZE 4096

#else

#define MIN_PAGE_SIZE 1024

#endif

#define MAX_PAGE_SIZE 16384

#define DEFAULT_PAGE_SIZE 4096

В данном листинге определяются размеры страницы: её максимальный и минимальный размер, а так же размер по умолчанию.

/* Basic page header */

Основной заголовок страницы

typedef struct pag {

SCHAR pag_type;

SCHAR pag_flags;

USHORT pag_checksum;

ULONG pag_generation;

ULONG pag_seqno; /*WAL seqno of last update*/

ULONG pag_offset; /*WAL offset of last update*/

} *PAG;

Определяется: тип страницы, флаги, контрольная сумма, номер последней измененной последовательности и смещение последнего изменения.

/* Data Page */

Определяется заголовок страницы данных

typedef struct dpg {

struct pag dpg_header;

SLONG dpg_sequence;

USHORT dpg_relation;

USHORT dpg_count;

struct dpg_repeat

{

USHORT dpg_offset;

USHORT dpg_length;

} dpg_rpt [1];

} *DPG;

Сначала идет стандартный заголовок страницы, далее следует номер последовательности в отношении, номер отношения, количество записей на странице. Далее следует повторяющаяся структура { смещение, длина } фрагмента записи.

/* Record header */

Заголовок записи

typedef struct rhd {

SLONG rhd_transaction;/* transaction id */

SLONG rhd_b_page; /* back pointer */

USHORT rhd_b_line; /* back line */

USHORT rhd_flags; /* flags, etc */

UCHAR rhd_format; /* format version */

#ifdef _CRAY

UCHAR rhd_pad [7];

#endif

UCHAR rhd_data [1];

} *RHD;

В заголовке записи определен номер транзакции, указатель на старую версию записи, флаги, версию формата записи и непосредственно данные.

Так же из файла ods . h можно узнать структуру фрагментированных записей и структуру записей поля blob . Но в данной статье мы не будем их рассматривать.

Далее определяются флаги записей

# define rhd _ deleted 1

Запись логически удалена

#define rhd_chain 2

Запись является старой версией

#define rhd_fragment 4

Запись является фрагментом

#define rhd_incomplete 8

Запись неполная

#define rhd_blob 16

Поле типа blob

#define rhd_delta 32

Предыдущая версия, различия только

#define rhd_large 64

Объект является большим

#define rhd_damaged 128

Объект известен , как поврежденный

#define rhd_gc_active 256

Мусор, мертвая версия записи

Как говорилось, ранее данные в записях сжаты методом кодирования переменной длины ( RLE ). Суть метода заключается в том, что положительное число указывает на количество следующих байт, которые непосредственно следует прочитать, отрицательное же число указывает, что следующий байт нужно повторить abs ( n ) (где abs - модуль числа) раз.

Заголовок хранит информацию о транзакции, которая требуется для реализации multi - generational архитектуры, в нормальной записи заголовок будет только содержать указатель на старую версию.

После заголовка и перед данными идет вектор нулевых бит на каждое поле (добитое до 8 (битовой) байтовой границы) в таблице. Если флаг установлен, тогда поле равно нулю, пока данные установлены в 0 для улучшения компрессии. Это означает что заголовок состоит из эффективной длины 16 байт и 3 байта были добавлены для выравнивания. Байты для нулевого битового вектора добавляются по необходимости, в зависимости от количества полей, определенных в таблице.

В результате того, что резервируется место для версий, когда заканчивается место на странице, остается возможность того, что новая версия записи будет сохранена на этой же странице.

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

Напомню вам, что на платформе intel при записи числа типа word (два байта), сначала записывается младший байт, а затем старший, например число 0 x 5684 будет представлено на диске, как 84:56. Аналогично сохраняются и числа типа long .

Формат даты является необычным по сравнению со стандартными типами записи дат, поэтому немного остановимся на его описании.

Для хранения даты и времени в Interbase существует тип date, его внутреннее представление таково. Это запись из двух 32-разрядных знаковых целых чисел. В первом числе хранится число дней, прошедших с 17 ноября 1858, а во втором - время в десятых долях миллисекунды, прошедшее после полуночи.

Для перевода в стандартный тип Delphi - TDateTime, который объявлен как TDateTime = type Double, где целая часть - это число дней, прошедших с 30 декабря 1899, а дробная часть - время, прошедшее после полуночи (.0 = 0:00; .25 = 6:00; .5 = 12:00; .75 = 18:00) можно воспользоваться простой формулой

DateTime := Days - IBDateDelta + MSec10 / MSecsPerDay10, где

Days - количество дней в формате Interbase ;

IBDateDelta = 15018 - разница в днях между датами Delphi и Interbase;

MSec 10 - время в десятых долях миллисекунды, прошедшее после полуночи;

MSecsPerDay10 = Количество миллисекунд в сутках * 10

На данный момент мы в достаточной степени узнали необходимую информацию о том, каким образом и где хранится информация в препарируемой нами базе данных. Изучив подробно все структуры используемые СУБД было бы возможно написать программу и воочию посмотреть и восстановить все данные своими руками. Но, как говорится, <Кесарю - кесарево>, я же предпочитаю воспользоваться стандартной программной isq ( wisq , кому как нравится) и движком interbase (ведь не зря его писали?) если, конечно база данных не повреждена, и тем самым ускорить процесс восстановления информации.

SQL- запросы

Далее мы рассмотрим sql -запросы, которые необходимы нам для восстановления утерянной информации.

select rdb$relation_fields.rdb$field_name, rdb$relation_fields.rdb$field_id, rdb$fields.rdb$field_length, rdb$types.rdb$type_name from rdb$relation_fields left join rdb$fields on rdb$relation_fields.rdb$field_source= rdb$fields.rdb$field_name left join rdb$types on rdb$types.rdb$type=rdb$fields.rdb$field_type where (rdb$relation_name=' MYTABLE ') and (rdb$types.rdb$field_name= 'RDB$FIELD_TYPE')

Данный запрос дает нам ответ на один из главных вопросов, как расположены поля в записи таблицы MYTABLE , какой имеют размер и тип

Пример выборки :

RDB$
FIELD_NAME

RDB$
FIELD_ID

RDB$
FIELD_LENGTH

RDB$
TYPE_NAME

field 1

0

4

long

field2

1

2

float

field3

3

8

double

field4

4

1

varying

В первом столбце указано название поля, во втором его порядок в записи, в третьем длина поля в байтах, а в четвертом тип поля.

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

select rdb$relation_id,rdb$relation_name, RDB$PAGE_NUMBER, rdb$page_type from rdb$pages left join RDB$relations on rdb$pages. RDB$RELATION_ID=RDB$relations.RDB$RELATION_ID where rdb$relation_name=' MYTABLE ';

Пример выборки:

RDB$
RELATION_ID

RDB$
RELATION_NAME

RDB$
PAGE_NUMBER

RDB$
PAGE_TYPE

145

MYTABLE

198

4

145

MYTABLE

199

6

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

На самом деле, <увы и ах>, мы только <добыли> из базы номера страниц указателя и индекса, а не полностью список всех страниц. Так что, засучим рукава и будем работать далее. Страница индексов для нас никоем образом не важна, а формат страницы указателя был вкратце описан выше.

Остановимся на нем более подробно.

typedef struct ppg {
   struct pag ppg_header;
   SLONG ppg_sequence; /* Sequence number in relation */
   SLONG ppg_next; /* Next pointer page in relation */
   USHORT ppg_count; /* Number of slots active */
   USHORT ppg_relation; /* Relation id */
   USHORT ppg_min_space;/* Lowest slot with space available */
   USHORT ppg_max_space;/* Highest slot with space available */
   SLONG ppg_page [1]; /* Data page vector */ } *PPG;  
#define ppg_eof 1 /* Last pointer page in relation */

В стандартном заголовке для нас особо интересно поле flag , если как описано выше, оно установлено в <1>, то это значит что таблица указателей является последней для рассматриваемого отношения (таблицы), и соответственно мы можем узнать все необходимые нам данные.

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

Далее идет вектор данных, в котором непосредственно и перечислены страницы.

Всё вышеописанное можно наглядно посмотреть с помощью программы IBSurgeon Viewer .

Итак, что мы знаем в данный момент:

•  структуру таблицы - каким образом расположены в ней поля и их размер;
•  номера страниц данных, в которых находится наша таблица;
•  структуру страниц данных.

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

Восстановление данных

И так мы можем приступать к самому ответственному этапу. Для удобства работы можно порезать файл базы данных на множество страниц и вычленить страницы с необходимой нам таблицей. Для начала пусть это будет любая таблица с текстовыми данными, например справочник товаров и т.д. Просмотрев его любым текстовым редактором можно заметить, что в данном файле находятся утерянные текстовые данные. Но возникает другой вопрос, как эти данные вытащить из базы, если заголовок со смещением и размером записи утерян навсегда. Возможно придумать массу алгоритмов с помощью которых можно восстановить утерянные данные, я предлагаю, на мой взгляд, самый простой, да и по вычислительным возможностям современных компьютеров восстановление происходит относительно быстро. В качестве примера данного утверждения могу сказать, что восстановление 2000 записей (не текстовая таблица) на Athlon XP 2000, программой написанной на Delphi , заняло порядка 10 секунд.

Суть алгоритма состоит в том, что нам известно следующее:

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

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

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

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

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

•  Если первичным ключом таблицы является поле с автоинкриментом, то соответственно оно не должно быть более чем значение генератора, так же, если не определено иное оно не может быть отрицательным;

•  Даты, если это, например, простенькая складская программа, не могут быть в далёком прошлом и далёком будущем;

•  Цена на товар не может быть отрицательной либо слишком большой;

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

В качестве примера могу сказать, что при восстановлении 2000 записей имеющими дубликаты оказались только 4.

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

В результате проделанной работы мы получаем из базы всё что нужно.

Для простоты восстановления можно просто приостановить работу всех триггеров и с помощью sql запросов добавить недостающие данные.

ГОТОВО!!!!

Новости мира IT:

Архив новостей

Последние комментарии:

Loading

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...