В процессе интеграции систем и обмена данными разработчики 1С часто сталкиваются с неочевидным поведением платформы. В этой статье мы подробно разберем две серьезные проблемы, возникшие при выгрузке и последующей загрузке справочника номенклатуры через формат JSON. Сначала мы выясним причину появления загадочного двойного слэша в путях категорий, а затем детально проанализируем, почему при загрузке больших файлов в файловую базу данных возникает ошибка «Конфликт блокировок при выполнении транзакции» и как её эффективно устранить.
Рассмотрим ситуацию: при выгрузке иерархии номенклатуры в JSON-файл в именах групп, содержащих символ обратного слэша (например, Выключатели\розетки IP54), в итоговом файле внезапно появляется двойной слэш: Выключатели\\розетки IP54. При этом обычный прямой слэш (/) выгружается совершенно нормально.
Выясним причину этого явления. Символ обратного слэша (\) в спецификации формата JSON (а также во многих языках программирования) является служебным экранирующим символом. Он используется для обозначения спецсимволов внутри строк — например, перевода строки (\n), табуляции (\t) или кавычек (\").
Если внутри строкового значения встречается сам символ обратного слэша, стандарт JSON обязывает его экранировать. Чтобы сериализатор понял, что это просто текстовый символ, а не начало управляющей последовательности, он автоматически дублирует его. Таким образом, строка Выключатели\\розетки в файле JSON — это абсолютно корректное представление строки Выключатели\розетки. При чтении этого JSON-файла стандартными средствами 1С (через объект ЧтениеJSON) двойной слэш автоматически преобразуется обратно в одинарный. Никаких дополнительных обработок и замен делать не нужно.
Прямой слэш (/) по стандарту не требует обязательного экранирования, поэтому он записывается в файл как есть, без удвоения.
Перейдем ко второй, более сложной проблеме. При импорте большого JSON-файла с номенклатурой (объемом более 10 Мб, содержащего тысячи элементов) в файловую базу 1С процесс прерывается аварийно примерно на середине. Платформа выдает ошибку: «Конфликт блокировок при выполнении транзакции». Это происходит даже в тех случаях, когда загрузка выполняется монопольно и в базе больше нет активных пользователей.
Проанализируем архитектурные причины этого поведения и разберем по шагам, как исправить код загрузки.
В коде загрузки перед вызовом метода Записать() программисты часто пытаются принудительно вызвать метод Заблокировать():
НоваяНоменклатура.Заблокировать();
НоваяНоменклатура.Записать();
В сценарии пакетной программной загрузки данных в один поток явная объектная блокировка приносит исключительно вред. Метод Заблокировать() предназначен для интерактивной работы пользователей, чтобы предотвратить одновременное редактирование одного объекта двумя людьми в интерфейсе. При программной записи метод Записать() сам по себе устанавливает необходимую транзакционную блокировку базы данных.
При пакетном создании новых элементов вызов Заблокировать() перегружает транзакционный механизм файловой СУБД. Если в процессе создания элемента иерархически создаются папки, может возникнуть ситуация взаимной блокировки (deadlock), когда элемент ждет блокировки родителя, а родитель заблокирован процессом создания элемента. Решение: полностью удалите метод Заблокировать() из кода пакетной загрузки.
Это ключевая причина возникновения конфликтов блокировок. В представленном коде создания номенклатуры объект записывается в режиме обычной работы пользователя. Это означает, что при записи каждого элемента система 1С выполняет огромный объем невидимой работы:
ПередЗаписью и ПриЗаписи, встроенные в конфигурацию (проверки, заполнение связанных регистров, расчеты цен и т.д.).В файловой базе данных 1С блокировка таблиц происходит не на уровне конкретной записи (как в MS SQL или PostgreSQL), а на уровне всей таблицы целиком. Массовое создание элементов без отключения этой логики мгновенно и намертво блокирует таблицы регистрации изменений планов обмена, вызывая транзакционные тупики.
Решение этой проблемы заключается в использовании свойства ОбменДанными.Загрузка. Перед записью созданного элемента обязательно устанавливайте это свойство в значение Истина:
НоваяНоменклатура = Справочники.Номенклатура.CreateItem();
// ... Заполнение реквизитов ...
НоваяНоменклатура.ОбменДанными.Загрузка = Истина;
НоваяНоменклатура.Записать();
Установка этого флага сообщает платформе, что объект загружается программно. Система отключает большинство прикладных проверок, подписок на события и, самое главное, подавляет автоматическую регистрацию изменений в планах обмена. Это снижает нагрузку на базу данных в десятки раз и полностью устраняет транзакционные конфликты таблиц регистрации.
Рассмотрим физику работы файловой СУБД 1С. Если вы не открываете транзакцию в коде вручную, платформа вынуждена оборачивать каждый одиночный вызов метода Записать() в отдельную неявную транзакцию. Запись 10 000 элементов превращается в 10 000 транзакций. Каждая транзакция требует физического обращения к жесткому диску для фиксации изменений, что приводит к колоссальным задержкам и удержанию блокировок.
С другой стороны, попытка загрузить все 10 000 элементов в рамках одной гигантской транзакции приведет к переполнению оперативной памяти компьютера (так как файловая СУБД накапливает изменения текущей транзакции в ОЗУ) и вызовет падение платформы.
Оптимальным решением является группировка записи элементов в транзакции порциями (пакетами) по 100–500 элементов. Разберем шаблон реализации такого алгоритма:
&НаСервере
Процедура ЗагрузитьДанныеПорциями(СписокЭлементов)
КоличествоВТранзакции = 0;
РазмерПорции = 200; // Оптимальный размер пачки для файловой базы данных
НачатьТранзакцию();
Для Каждого ТекЭлемент Из СписокЭлементов Цикл
// Создаем и заполняем элемент номенклатуры
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = СокрЛП(ТекЭлемент.Наименование);
// ... заполнение остальных реквизитов ...
// Отключаем лишние обработчики и регистрацию изменений
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
Попытка
НовыйЭлемент.Записать();
КоличествоВТранзакции = КоличествоВТранзакции + 1;
Исключение
ОтменитьТранзакцию();
ЗаписатьВЛогОшибок("Не удалось записать: " + ТекЭлемент.Наименование + ". Ошибка: " + ОписаниеОшибки());
// Перезапускаем транзакцию для продолжения обработки остальных элементов
НачатьТранзакцию();
КоличествоВТранзакции = 0;
Продолжить;
КонецПопытки;
// Фиксируем порцию данных на диске и открываем новую транзакцию
Если КоличествоВТранзакции >= РазмерПорции Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КоличествоВТранзакции = 0;
КонецЕсли;
КонецЦикла;
// Завершаем последнюю открытую транзакцию, если в ней остались элементы
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
КонецПроцедуры
При импорте структуры папок и элементов «на лету» часто возникает ситуация, когда при создании элемента система пытается параллельно создать его родительскую группу. Это нарушает последовательность блокировок таблиц и вызывает сбои.
Наиболее надежный подход — разделить процесс загрузки на два независимых прохода:
Посмотрим на пример функции, которая создает структуру папок по переданному пути:
&НаСервере
Функция ПолучитьСоздатьРодителя(Знач ПутьКГруппе)
МассивПапок = СтрРазделить(ПутьКГруппе, "/");
Родитель = Справочники.Номенклатура.ПустаяСсылка();
Для Каждого ИмяПапки Из МассивПапок Цикл
ТекущаяПапка = Справочники.Номенклатура.НайтиПоНаименованию(ИмяПапки, Истина, Родитель);
Если ТекущаяПапка.Пустая() Тогда
НоваяГруппа = Справочники.Номенклатура.СоздатьГруппу();
НоваяГруппа.Наименование = ИмяПапки;
НоваяГруппа.Родитель = Родитель;
НоваяГруппа.ОбменДанными.Загрузка = Истина;
Попытка
НоваяГруппа.Записать();
Родитель = НоваяГруппа.Ссылка;
Исключение
ЗаписатьВЛог("Ошибка создания папки: " + ИмяПапки + ". " + ОписаниеОшибки());
Родитель = Справочники.Номенклатура.ПустаяСсылка();
Прервать;
КонецПопытки;
Иначе
Родитель = ТекущаяПапка;
КонецЕсли;
КонецЦикла;
Возврат Родитель;
КонецФункции
При частых аварийных остановках, вызванных конфликтами блокировок и переполнением транзакций, в файловой базе данных (файл 1Cv8.1CD) могут возникнуть повреждения логической структуры таблиц. Симптомом этого является внезапное появление ошибок вида «Ошибка СУБД: Невосстановимая ошибка при выполнении запроса POST к ресурсу /e1cib/logForm».
Если вы столкнулись с подобным поведением, выполните следующие шаги по реанимации базы данных:
1Cv8.1CD в безопасное место перед проведением любых манипуляций.bin) утилиту chdbfl.exe. Запустите ее, выберите файл базы данных, установите флаг «Исправлять обнаруженные ошибки» и выполните проверку физической целостности файла.Подводя итог, отметим, что корректная работа с файловыми базами 1С при импорте данных требует обязательного отключения тяжелой бизнес-логики через установку свойства ОбменДанными.Загрузка = Истина, управления транзакциями с помощью порционной записи объектов и деления иерархической структуры на отдельные этапы загрузки.