Аварийное отключение электроэнергии на сервере — один из самых опасных сценариев для файловых баз данных формата DBF, используемых в платформе 1С:Предприятие 7.7. Внезапная остановка системы, особенно при некорректной работе источников бесперебойного питания (ИБП) или дисковых массивов RAID без батарейной защиты (BBU), приводит к рассинхронизации таблиц, разрушению индексов и потере транзакционных данных.
В этой статье мы подробно разберем, почему происходят подобные сбои, как диагностировать повреждения таблиц базы данных, и рассмотрим эффективные методы восстановления работоспособности системы на примере конфигурации Торговля и Склад 7.7 (ТиС).
При анализе поврежденного файла 1SJOURN.dbf (системный журнал документов) после аварийного перезапуска сервера часто обнаруживается, что числовые поля, поля дат и служебные признаки заполнены обрывками обычного текста (например, «еко», «рректные», «записи», «тега»). Выясним причину этого явления.
Современные операционные системы и аппаратные RAID-контроллеры используют технологию отложенной записи (Write-Back Cache). Данные, которые 1С отправляет на запись, сначала попадают в быстрый кэш оперативной памяти (системный кэш ОС или кэш RAID-контроллера) и лишь спустя некоторое время физически сбрасываются на жесткие диски. Если в момент нахождения данных в кэше происходит мгновенное обесточивание сервера, то страницы памяти перемешиваются.
При последующем запуске операционная система пытается восстановить поврежденную файловую систему (например, NTFS) с помощью утилит типа CHKDSK. В процессе восстановления утилита может записать фрагменты системных логов, текстовых файлов или дампов памяти (где содержались логи работы ККТ и ОФД со словами «некорректные записи тега») непосредственно в физические секторы, выделенные под таблицы 1С. В результате заголовки и записи DBF-файлов оказываются перезаписаны случайным «мусором».
Часто возникает ситуация: кассиры успешно пробили чеки, они ушли в ОФД и отображаются в Журнале регистрации (ЖР) 1С, но при попытке открыть документ из ЖР система выдает ошибку «Объект не найден». Проанализируем, почему так происходит.
Журнал регистрации в 1С 7.7 представляет собой простой текстовый файл 1cv7.mlg, расположенный в каталоге SysLog. Запись в него производится последовательно методом добавления строк в режиме реального времени. Поскольку текстовый файл пишется напрямую и не требует сложных транзакционных блокировок нескольких таблиц, запись о проведении чека ККМ успела физически сохраниться на диске до отключения питания.
В то же время, создание полноценного документа в базе данных требует синхронной записи сразу в несколько таблиц DBF:
1SJOURN.dbf — общая информация о документе (дата, время, номер, проведен/помечен на удаление).DH* (например, DH1345.dbf) — таблица шапки документа (клиент, склад, касса, сумма).DT* (например, DT1345.dbf) — таблица табличной части документа (номенклатура, количество, цена).RA* и RG*).В момент сбоя транзакция по записи этих таблиц осталась в энергозависимом кэше диска и не была завершена. Документ физически исчез из таблиц СУБД, хотя запись в текстовом логе ЖР о его создании осталась.
При запуске тестирования базы данных система может выдать предупреждения вида: 1SJOURN - Неверный признак удаленности записи. Посмотрим на внутреннюю структуру формата DBF, чтобы понять физический смысл этой ошибки.
Каждая запись (строка) в файле формата DBF начинается со специального служебного байта (маркера удаления). Этот байт может принимать только два валидных значения:
0x20) — запись является активной (живой).0x2A) — запись помечена как удаленная (в 1С это означает физически удаленную запись, ожидающую упаковки таблицы).Если из-за сбоя файловой системы в этот первый байт записывается любой другой символ (буква, цифра, знак препинания из системного мусора), платформа 1С теряет способность определить статус строки и классифицирует это как нарушение физической целостности таблицы DBF.
Штатная процедура «Тестирование и исправление» (ТиИ) в Конфигураторе на больших базах данных может выполняться часами, что неприемлемо при жестких временных рамках (например, когда магазин должен открыться через несколько часов). Более того, при выборе опции «Удалять битые объекты» ТиИ может безвозвратно затереть важные логические связи.
Рассмотрим альтернативный, профессиональный алгоритм быстрого восстановления базы данных вручную:
1SJOURN.dbf) в специализированном DBF-редакторе (DBF Commander, DBFNavigator). Поврежденные записи обычно находятся в самом конце таблицы.DH*) и табличных частей (DT*) документов уцелели, мы можем восстановить записи в 1SJOURN.dbf программно. Для этого используем внешние обработки автоматического воссоздания журнала (например, утилиту restjourn.ert), которая сканирует таблицы документов и генерирует для них корректные строки в системном журнале.*.CDX из каталога базы данных. При первом запуске 1С в монопольном режиме система автоматически переиндексирует базу данных, создав чистые и актуальные индексы.ПересчетИтогов()).Если часть чеков ККМ безвозвратно утеряна в базе 1С, но физически они прошли через фискальный накопитель и были отправлены оператору фискальных данных (ОФД), мы можем восстановить их. Самый простой путь — выгрузить чеки из личного кабинета ОФД в формате JSON и загрузить их обратно в 1С.
Проанализируем структуру JSON-документа ОФД и сопоставим его ключевые теги с реквизитами документов в 1С:
1012 — Дата и время совершения расчета. Используем для установки даты и времени документа 1С.1021 — ФИО кассира. Используется для заполнения реквизита «Автор» или «Кассир».1020 — Общая сумма чека (передается в копейках, значение необходимо разделить на 100).1030 — Наименование товара. Используется для поиска номенклатуры по наименованию или артикулу.1023 — Количество товара в строке.1079 — Цена за единицу товара (в копейках).1043 — Сумма по товарной позиции (в копейках).Рассмотрим пример программного разбора JSON-файла ОФД в среде 1С:Предприятие 7.7 с использованием стандартного системного объекта ScriptControl. Этот метод позволяет автоматизировать процесс создания утерянных документов без ручного ввода кассирами.
// Процедура автоматического импорта чека из JSON-файла ОФД
Процедура ЗагрузитьЧекИзJSON(ПутьКФайлу)
// Создаем объект для работы со скриптами JScript
JS = СоздатьОбъект("MSScriptControl.ScriptControl");
JS.Language = "JScript";
// Добавляем вспомогательные функции для доступа к свойствам объектов JS
JS.AddCode("function getProp(obj, key) { return obj[key]; }");
JS.AddCode("function getArrayElem(arr, idx) { return arr[idx]; }");
JS.AddCode("function getArrayLength(arr) { return arr.length; }");
// Читаем содержимое JSON-файла
ТекстДокумента = СоздатьОбъект("Текст");
ТекстДокумента.Открыть(ПутьКФайлу);
СтрокаJSON = "";
Для Ном = 1 По ТекстДокумента.КоличествоСтрок() Цикл
СтрокаJSON = СтрокаJSON + ТекстДокумента.ПолучитьСтроку(Ном);
КонецЦикла;
Попытка
// Парсим строку JSON в объект JavaScript
ЧекJS = JS.Eval("(" + СтрокаJSON + ")");
// Извлекаем основные реквизиты чека
ДатаВремяСтрока = JS.Run("getProp", ЧекJS, "1012"); // Пример: "2020-06-08T21:33:00"
ФИОКассира = JS.Run("getProp", ЧекJS, "1021");
ОбщаяСуммаКоп = JS.Run("getProp", ЧекJS, "1020");
ОбщаяСумма = ОбщаяСуммаКоп / 100;
// Создаем новый документ ЧекККМ в 1С 7.7
ДокЧек = СоздатьОбъект("Документ.ЧекККМ");
ДокЧек.Новый();
ДокЧек.УстановитьВремя(Сред(ДатаВремяСтрока, 12, 2), Сред(ДатаВремяСтрока, 15, 2), Сред(ДатаВремяСтрока, 18, 2));
ДокЧек.ДатаДок = РабочаяДата(); // или преобразуем дату из строки
ДокЧек.Комментарий = "Восстановлен автоматически из ОФД. Кассир: " + ФИОКассира;
// Получаем массив позиций чека (тег 1059)
Позиции = JS.Run("getProp", ЧекJS, "1059");
РазмерМассива = JS.Run("getArrayLength", Позиции);
Для Инд = 0 По РазмерМассива - 1 Цикл
СтрокаТовара = JS.Run("getArrayElem", Позиции, Инд);
НазваниеТовара = JS.Run("getProp", СтрокаТовара, "1030");
Количество = JS.Run("getProp", СтрокаТовара, "1023");
ЦенаКоп = JS.Run("getProp", СтрокаТовара, "1079");
СуммаКоп = JS.Run("getProp", СтрокаТовара, "1043");
Цена = ЦенаКоп / 100;
Сумма = СуммаКоп / 100;
// Ищем номенклатуру в справочнике по наименованию
СпрНоменклатура = СоздатьОбъект("Справочник.Номенклатура");
Если СпрНоменклатура.НайтиПоНаименованию(НазваниеТовара, 0, 1) = 1 Тогда
ТоварСсылка = СпрНоменклатура.ТекущийЭлемент();
Иначе
// Если товар не найден, можно использовать предопределенный "Пустой товар" или создавать новый
ТоварСсылка = ПолучитьПустойТовар();
Сообщить("Внимание! Товар '" + НазваниеТовара + "' не найден в справочнике номенклатуры.");
КонецЕсли;
// Заполняем табличную часть документа
ДокЧек.НоваяСтрока();
ДокЧек.Номенклатура = ТоварСсылка;
ДокЧек.Количество = Количество;
ДокЧек.Цена = Цена;
ДокЧек.Сумма = Сумма;
КонецЦикла;
// Записываем и проводим документ
ДокЧек.Записать();
ДокЧек.Провести();
Сообщить("Успешно восстановлен Чек на сумму: " + ОбщаяСумма + " руб.");
Исключение
Предупреждение("Ошибка парсинга или записи документа: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Для предотвращения повторения подобных инцидентов и минимизации рисков потери данных в будущем рекомендуется внедрить следующие организационно-технические меры: