Каждый разработчик, поддерживающий конфигурации на платформе 1С:Предприятие 7.7, рано или поздно сталкивается с загадочными явлениями. Одно из самых опасных и труднообъяснимых — это внезапное исчезновение позиций номенклатуры (или элементов других справочников) с одновременным появлением «битых» дублей других элементов. При этом в Журнале регистрации нет никаких записей об удалении или изменении этих объектов, тестирование и исправление (ТИИ) не находит ошибок, а стандартная переиндексация базы данных не решает проблему.
В этой статье мы подробно разберем природу этого критического платформенного бага, выясним физический механизм разрушения файлов таблиц формата DBF, а также рассмотрим практические способы восстановления данных и программные методы обхода этой уязвимости.
---
Проанализируем типичный сценарий проявления этой ошибки на примере справочника Номенклатура:
Выясним, почему стандартные средства СУБД и встроенные механизмы 1С 7.7 не сигнализируют о проблеме. Дело в том, что физического удаления строки из таблицы базы данных не происходит. Происходит некорректная перезапись существующей строки данных другой информацией, из-за чего нарушается целостность индексов и логических связей.
---
ИспользоватьСписокЭлементов()Причиной этой проблемы является общеизвестный, но официально так и не исправленный платформенный баг ядра 1С 7.7. Он напрямую связан с совместным использованием метода ИспользоватьСписокЭлементов() и интерактивного изменения данных в форме списка.
Рассмотрим стандартный программный код, который часто используют для реализации быстрого поиска или фильтрации элементов в форме списка справочника:
Процедура ПоискПоПодстроке(Реж)
Если (ПустоеЗначение(СокрЛП(ПодСтрока))=1) и (Реж=1) Тогда
ИспользоватьСписокЭлементов();
Возврат;
КонецЕсли;
СохрИсторию();
Если Реж=1 Тогда
СписокНом = глПоискПоСтрокеНомВозвратСписка(ПодСтрока);
СписокНом.Сортировать();
Если ПустоеЗначение(СписокНом)=0 Тогда
ИерархическийСписок(0);
ИспользоватьСписокЭлементов(СписокНом);
КонецЕсли;
ИначеЕсли Реж=2 Тогда
Параметр = СоздатьОбъект("СписокЗначений");
Параметр.ДобавитьЗначение(СокрЛП(Подстрока),"Подстрока");
Параметр.ДобавитьЗначение("Справочник.Номенклатура","Источник");
Параметр.ДобавитьЗначение("Номенклатура","ВидИсточника");
ОткрытьФормуМодально("Отчет",Параметр,КаталогИБ() + "ExtForms\мои\НоменклатураОбрабГруппТиповая.ert");
Если ТипЗначенияСтр(Параметр) <> "СписокЗначений" Тогда
Возврат;
КонецЕсли;
Подстрока = Параметр.Получить("Подстрока");
СписокВрем = Параметр.Получить("СписокТоваров");
Если ТипЗначенияСтр(СписокВрем)="СписокЗначений" Тогда
Если СписокВрем.РазмерСписка()>0 Тогда
СписокВрем.Сортировать();
Если ПустоеЗначение(СписокВрем)=0 Тогда
ИерархическийСписок(0);
ИспользоватьСписокЭлементов(СписокВрем);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Когда мы накладываем на форму списка фильтр с помощью метода ИспользоватьСписокЭлементов(СписокНом), платформа строит виртуальное представление данных. Если в этот момент пользователь решает отредактировать элемент непосредственно в списке, создать новый элемент копированием или просто открыть карточку элемента на редактирование и нажать кнопку "Записать", внутренний механизм позиционирования СУБД дает сбой.
Вместо того чтобы обновить физическую запись редактируемого элемента, DBF-драйвер 1С позиционируется на другую строку таблицы (которая физически находится рядом, но скрыта текущим фильтром) и записывает новые данные поверх нее. В результате:
ID, поле родителя PARENTID) ломаются, так как перезапись происходит частично. В результате индексы «сходят с ума», и элементы становятся «битыми призраками».---
Если сбой уже произошел, восстановить потерянный элемент стандартным «Тестированием и исправлением» невозможно. Нам придется прибегнуть к прямому редактированию файлов базы данных с помощью DBF-редактора (например, DBF Navigator или аналогичного софта). Выполним восстановление по шагам:
ID (например, это 36-ричный код вида " 1B34").SC123.DBF, предварительно определив имя файла таблицы в словаре данных 1CV7.DD) в DBF-редакторе. Находим строки по коду задвоившейся номенклатуры.ID в колонке физического идентификатора таблицы не будет соответствовать логическим реквизитам, либо в нем будет содержаться тот самый старый ID пропавшей позиции (в нашем примере " 1B34"), тогда как все текстовые поля (наименование, артикул) затерты данными «задвоенного» элемента.ID записи.*.CDX из каталога базы данных и запускаем 1С:Предприятие в монопольном режиме для полной переиндексации.Важное предупреждение: Перед любыми манипуляциями с файлами таблиц напрямую обязательно делайте резервную копию всей базы данных!
---
Поскольку платформа 1С 7.7 давно снята с поддержки и обновлений ядра ожидать не приходится, разработчикам необходимо программно исключить условия возникновения этого бага.
Самый простой и надежный административный способ борьбы со сбоем:
Номенклатура (и других справочников, где используется данный фильтр) и снимите галочку «Редактировать в списке».Если пользователям жизненно необходимо редактировать элементы, мы можем программно отключать опасный фильтр в момент открытия формы элемента и возвращать его обратно после закрытия. Рассмотрим, как это реализовать.
В форме списка справочника перехватываем открытие элемента и перед вызовом формы сбрасываем фильтрацию:
Процедура ПриОткрытииРедактирования()
// Перед тем как открыть карточку элемента на редактирование,
// временно отключаем список элементов во избежание платформенного бага
ИспользоватьСписокЭлементов();
КонецПроцедуры
После закрытия формы редактирования элемента (например, при активизации формы списка или при ее обновлении) мы можем снова наложить фильтр ИспользоватьСписокЭлементов(СписокНом). Это обезопасит базу данных от ложного позиционирования драйвера СУБД.
Если вам требуется реализовать сложный поиск или подбор элементов, откажитесь от стандартного отображения справочника в пользу Таблицы Значений (ТЗ), размещенной на форме:
НайтиЭлемент() и открываем его форму:
ОткрытьФорму(ТекущийЭлемент);
Этот подход на 100% защищает физические таблицы базы данных, так как метод ИспользоватьСписокЭлементов() вообще не вызывается.
Для крупных и высоконагруженных систем лучшим решением является использование внешней компоненты 1С++. С помощью класса ТабличноеПоле можно связать сетку на форме с прямым SQL-запросом к базе данных (даже если она работает в DBF-формате через драйверы). Это не только навсегда решает проблему порчи данных, но и ускоряет работу интерфейса подбора номенклатуры в десятки раз.
---
Платформенная ошибка с методом ИспользоватьСписокЭлементов() в 1С 7.7 — классический пример того, как удобный встроенный функционал может стать причиной критического повреждения базы данных. Используя предложенные нами архитектурные обходы (запрет редактирования в списке, программный сброс фильтров или вывод данных через ТЗ), вы сможете гарантировать абсолютную сохранность данных и стабильную работу вашей системы.