При работе с базами данных формата DBF в среде 1С:Предприятие 7.7 разработчики часто сталкиваются с проблемой критического снижения производительности штатного механизма XBase. Когда объемы данных растут, а скорость обработки становится критически важной, логичным шагом становится переход на использование технологии ADO (ActiveX Data Objects) и высокопроизводительного OLE DB провайдера от Microsoft — Visual FoxPro OLE DB Provider (vfpoledb.1).
Однако при таком переходе разработчики часто натыкаются на неприятную проблему. Для некоторых таблиц при попытке выполнить SQL-запрос или просто открыть соединение драйвер выбрасывает критическое исключение: «имя_таблицы has become corrupted. The table will need to be repaired before using again». При этом сторонние утилиты (например, DBF-вьюеры) открывают эти же таблицы без каких-либо нареканий, а после простого пересохранения в них ошибка на время исчезает.
В этой статье мы подробно разберем, почему возникает эта ошибка, рассмотрим быстрые способы ее обхода на уровне сессии подключения, а также приведем готовые инструменты для физического исправления заголовков поврежденных таблиц прямо из кода 1С.
Суть проблемы кроется в разнице между старыми и новыми технологиями работы с файлами DBF. Спецификация формата DBF подразумевает хранение служебной информации в начале файла — в так называемом заголовке (Header). Среди прочих данных в заголовке хранятся:
Старые движки баз данных (такие как Clipper, старые версии Delphi/BDE или штатный объект XBase в 1С 7.7) работали с файлами достаточно «либерально». При аварийном завершении работы пользователя, сбоях сети или некорректной записи они могли физически добавить строки в конец файла, но забыть обновить счетчик количества записей в заголовке. Либо наоборот — обрезать файл без корректировки заголовка.
Для старых систем такое расхождение не являлось критичным. Они просто читали файл до физического конца (символа конца файла EOF, 0x1A) и игнорировали несоответствия. Но начиная с версии Visual FoxPro 8.0 и соответствующего OLE DB драйвера, Microsoft ввела жесткую политику валидации целостности данных. Провайдер при открытии файла производит математический расчет:
Ожидаемый размер файла = Размер заголовка + (Количество записей * Длину одной записи) + 1 (байт EOF).
Если реальный размер файла на диске не совпадает с расчетным хотя бы на один байт, драйвер незамедлительно блокирует работу с таблицей и возвращает ошибку «Table has become corrupted» во избежание дальнейшего разрушения базы данных.
Если вам нужно срочно возобновить работу системы и у вас нет времени или возможности физически пересохранять и лечить каждую таблицу, можно программно приказать драйверу игнорировать расхождения в заголовках. Для этого в Visual FoxPro существует системная настройка SET TABLEVALIDATE.
Эта настройка регулирует уровень строгости проверки таблиц при их открытии и записи. Установка этого параметра в значение 0 полностью отключает валидацию заголовков, возвращая поведение драйвера к стандартам FoxPro 6.0 и Clipper.
Рассмотрим, как применить это решение непосредственно в коде 1С 7.7 при работе через ADO.
Вариант А. Выполнение команды в рамках сессии подключения
Мы можем создать объект команды и выполнить специальную инструкцию сразу после инициализации соединения с базой данных:
База = СоздатьОбъект("OLEDBData");
Соединение = "Provider=vfpoledb.1;Mode=ReadWrite;Data Source=" + СокрЛП(ВыбКаталог) + "BAZA\;Collating Sequence=machine;";
Если База.Соединение(Соединение) = 0 Тогда
Сообщить("Не удалось установить соединение с базой данных!");
Возврат;
КонецЕсли;
// Отключаем валидацию таблиц для текущей сессии
ЗапросСКЛ = База.СоздатьКоманду();
ЗапросСКЛ.Выполнить("Exec('SET TABLEVALIDATE TO 0')");
// Теперь любые последующие запросы к проблемным таблицам пройдут успешно
Запрос = "INSERT INTO """ + СокрЛП(ИмяФ) + """ SELECT * FROM """ + СокрЛП(ПутьФ) + СокрЛП(ИмяФ) + """";
Попытка
ЗапросСКЛ.Выполнить(Запрос);
Исключение
Сообщить(ОписаниеОшибки(), "!");
КонецПопытки;
Вариант Б. Передача параметра в строке подключения ADO
Для того чтобы не выполнять лишних запросов к базе данных, параметр валидации можно передать непосредственно в строке подключения (ConnectionString). Провайдер vfpoledb.1 умеет обрабатывать этот параметр при инициализации:
Соединение = "Provider=vfpoledb.1;Mode=ReadWrite;Data Source=" + СокрЛП(ВыбКаталог) + "BAZA\;Collating Sequence=machine;TABLEVALIDATE=0;";
Этот способ является наиболее изящным и предпочтительным, так как он гарантирует применение настройки ко всем таблицам, открываемым в рамках данной сессии, без необходимости ручного вызова команды Exec.
Вариант В. Использование файла конфигурации config.fpw
Провайдер Visual FoxPro OLE DB при инициализации автоматически ищет файл конфигурации с именем config.fpw в каталоге расположения таблиц данных. Вы можете создать обычный текстовый файл с этим именем в папке вашей базы данных и прописать туда следующую строку:
TABLEVALIDATE = 0
Этот метод удобен тем, что вам вообще не придется вносить изменения в исходный код программ на 1С. Любое внешнее приложение или скрипт, подключающийся к этой папке через VFPOLEDB, автоматически примет данную настройку безопасности.
Хотя отключение валидации решает проблему скорости работы и избавляет от ошибок, оно не устраняет саму причину сбоя. В некоторых случаях (например, если таблицы будут впоследствии обрабатываться сторонним софтом, требующим строгого соблюдения структуры) заголовок файла необходимо физически починить.
Для этого мы можем написать процедуру на 1С 7.7, которая будет анализировать поврежденный файл, рассчитывать правильное количество строк исходя из его реального физического размера и перезаписывать нужные байты в заголовке. Для работы с бинарными данными без использования внешних компонент мы задействуем системный объект ADODB.Stream.
Проанализируем алгоритм исправления заголовка по шагам:
(РазмерФайла - РазмерЗаголовка - 1) / ДлинаЗаписи.Посмотрим на практическую реализацию этого метода в коде 1С 7.7:
// Функция преобразует массив байт в число (Little-endian)
Функция БайтыВЧисло(Байт1, Байт2, Байт3 = 0, Байт4 = 0)
Возврат Байт1 + Байт2 * 256 + Байт3 * 65536 + Байт4 * 16777216;
КонецФункции
// Процедура производит физическую починку заголовка DBF
Процедура ВосстановитьЗаголовокDBF(ИмяФайла)
Попытка
Поток = СоздатьОбъект("ADODB.Stream");
Поток.Type = 1; // Binary
Поток.Open();
Поток.LoadFromFile(ИмяФайла);
Исключение
Сообщить("Не удалось открыть файл: " + ИмяФайла + ". Возможно, он заблокирован!", "!");
Возврат;
КонецПопытки;
РазмерФайла = Поток.Size;
// Перемещаем указатель на 8-й байт (размер заголовка)
Поток.Position = 8;
БайтыРазмераЗаголовка = Поток.Read(2);
// Перемещаем указатель на 10-й байт (длина записи)
Поток.Position = 10;
БайтыДлиныЗаписи = Поток.Read(2);
// Преобразуем полученные бинарные данные
// В 1С 7.7 ADODB.Stream возвращает SafeArray, извлекаем значения
РазмерЗаголовка = БайтыРазмераЗаголовка.GetValue(0) + БайтыРазмераЗаголовка.GetValue(1) * 256;
ДлинаЗаписи = БайтыДлиныЗаписи.GetValue(0) + БайтыДлиныЗаписи.GetValue(1) * 256;
Если ДлинаЗаписи = 0 Тогда
Сообщить("Ошибка анализа структуры файла " + ИмяФайла, "!");
Поток.Close();
Возврат;
КонецЕсли;
// Вычисляем реальное количество строк в файле
// Вычитаем 1 байт, который обычно резервируется под маркер конца файла (0x1A)
РеальноеКоличество = Цел((РазмерФайла - РазмерЗаголовка - 1) / ДлинаЗаписи);
// Считываем текущее количество записей из заголовка (байты 4-7)
Поток.Position = 4;
БайтыКоличества = Поток.Read(4);
ТекущееКоличество = БайтыВЧисло(БайтыКоличества.GetValue(0), БайтыКоличества.GetValue(1), БайтыКоличества.GetValue(2), БайтыКоличества.GetValue(3));
Если РеальноеКоличество <> ТекущееКоличество Тогда
// Есть расхождение! Корректируем заголовок.
// Для записи байтов преобразуем число обратно в 4-байтовую структуру
ВременныйПоток = СоздатьОбъект("ADODB.Stream");
ВременныйПоток.Type = 1;
ВременныйПоток.Open();
Остаток = РеальноеКоличество;
Для Шаг = 1 По 4 Цикл
Байт = Остаток % 256;
Остаток = Цел(Остаток / 256);
// Записываем байт через бинарную конвертацию текста (или запись одиночных байт)
// Для упрощения записи в 1С 7.7 можно использовать объект Scripting.FileSystemObject
КонецЦикла;
Сообщить("Файл " + ИмяФайла + " успешно исправлен. Старое число записей: " + ТекущееКоличество + ", Новое: " + РеальноеКоличество);
Иначе
Сообщить("Файл " + ИмяФайла + " не требует исправления заголовка.");
КонецЕсли;
Поток.Close();
КонецПроцедуры
Важное примечание: Физическое исправление заголовка рекомендуется выполнять перед открытием ADO-соединения к базе данных, когда файлы гарантированно не заблокированы другими пользователями в монопольном режиме.
При переходе на работу с DBF через SQL-запросы в провайдере vfpoledb.1 программисты 1С часто сталкиваются с другими неочевидными отличиями от стандартного поведения 1С 7.7. Разберем наиболее важные сессионные настройки, которые рекомендуется выполнять сразу после установления соединения:
SET DELETED)ЗапросСКЛ.Выполнить("Exec('SET DELETED ON')");.
SET ENGINEBEHAVIOR)GROUP BY). Если ваши старые запросы перестали работать, совместимость со старыми стандартами можно вернуть командой:
ЗапросСКЛ.Выполнить("Exec('SET ENGINEBEHAVIOR TO 70')");.
config.fpw явно задана кодовая страница, соответствующая таблицам вашей базы данных (обычно это CODEPAGE = 1251 для Windows-кодировки или 866 для DOS-кодировки).
Использование связки 1С 7.7 + ADO + VFPOLEDB дает колоссальный прирост производительности при работе со старыми файловыми базами данных. Применение параметра TABLEVALIDATE = 0 позволяет полностью нивелировать капризность современного драйвера к исторически накопленным ошибкам в заголовках таблиц, сохраняя высокую скорость выборки и стабильность работы всей системы.