Почему при автоматической загрузке заказов из XML в 1С создаются дубли и как это исправить

Программист 1С v8.3 (Управляемые формы)
← На главную

При автоматизации обмена данными программисты часто сталкиваются с ситуацией, когда одна и та же обработка, запущенная по расписанию, создает дублирующиеся документы. Самое загадочное в этой ситуации то, что в коде обычно присутствует проверка на существование объекта по уникальному идентификатору (ИД), но она «пропускает» создание второго экземпляра. Рассмотрим подробно, почему это происходит и как гарантированно избежать появления дублей в базе данных.

Анализируем причину: Состояние гонки (Race Condition)

Проанализируем ситуацию: в журнале регистрации фиксируются два разных сеанса, запущенных в одну и ту же секунду. Это классическое «состояние гонки». Когда два процесса 1cv8.exe или два фоновых задания начинают работу одновременно, происходит следующее:

  1. Первый сеанс читает XML, видит заказ с ИД 123, проверяет запросом в базе — «заказа нет».
  2. Второй сеанс в ту же миллисекунду читает тот же XML, видит тот же заказ с ИД 123, проверяет запросом — «заказа нет».
  3. Первый сеанс начинает записывать новый документ.
  4. Второй сеанс также начинает записывать новый документ.

Почему проверка «заказа нет» не сработала во втором сеансе? Потому что первый сеанс еще не завершил транзакцию записи. В системе 1С (и в большинстве СУБД) по умолчанию используется уровень изоляции транзакций Read Committed. Это означает, что один сеанс не увидит данные, которые записывает другой сеанс, пока тот не нажмет «фиксация транзакции». В итоге оба сеанса считают, что документа нет, и создают дубли.

Решение 1: Использование управляемых блокировок

Разберем самый правильный с точки зрения архитектуры 1С способ решения проблемы — установку исключительной управляемой блокировки на идентификатор заказа перед его поиском и созданием. Это заставит второй сеанс «подождать» на этапе проверки, пока первый сеанс не завершит запись.

Рассмотрим пример кода, который нужно внедрить в цикл обхода массива структур из XML:


Для Каждого ДанныеЗаказа Из МассивЗаказов Цикл
    
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("Документ.ЗаказКлиента");
    // Устанавливаем блокировку по нашему уникальному реквизиту
    // Важно: реквизит ИД должен быть индексирован!
    ЭлементБлокировки.УстановитьЗначение("ДополнительныйИД", ДанныеЗаказа.Ид);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    
    НачатьТранзакцию();
    Попытка
        Блокировка.Заблокировать();
        
        // Теперь выполняем поиск. Если другой сеанс уже создает такой заказ, 
        // текущий сеанс остановится на методе Заблокировать() и подождет.
        ЗаказОбъект = ПоискЗаказаПоИД(ДанныеЗаказа.Ид);
        
        Если ЗаказОбъект = Неопределено Тогда
            НовыйЗаказ = Документы.ЗаказКлиента.СоздатьДокумент();
            ЗаполнитьДанные(НовыйЗаказ, ДанныеЗаказа);
            НовыйЗаказ.Записать(РежимЗаписиДокумента.Запись);
        КонецЕсли;
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписатьВЛог("Ошибка при обработке заказа " + ДанныеЗаказа.Ид + ": " + ОписаниеОшибки());
    КонецПопытки;
    
КонецЦикла;

Решение 2: Переход на регламентные задания 1С

Выясним, почему внешние скрипты и планировщик задач Windows — не лучшее решение для 1С. Планировщик Windows может запустить вторую копию процесса, если предыдущая зависла или не успела завершиться (зависит от настроек «Run a new instance in parallel»).

Вместо этого разберем настройку Регламентного задания внутри самой конфигурации 1С:

  1. Создадим в метаданных новое РегламентноеЗадание.
  2. Укажем метод (процедуру в общем модуле), который выполняет загрузку.
  3. Настроим расписание прямо в консоли регламентных заданий.

Преимущество этого метода в том, что платформа 1С по умолчанию не запускает второй экземпляр того же регламентного задания, если первый еще выполняется. Это автоматически снимает проблему параллельных сеансов.

Решение 3: Атомарная операция с файлом-источником

Посмотрим на ситуацию со стороны файловой системы. Если мы не можем изменить логику блокировок в базе, мы можем использовать механизм блокировки самого файла XML. Рассмотрим алгоритм «захвата» файла:

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


ПутьКФайлу = "C:\Exchange\orders.xml";
ПутьВРаботе = "C:\Exchange\orders_proccessing.xml";

ФайлНаДиске = Новый Файл(ПутьКФайлу);
Если ФайлНаДиске.Существует() Тогда
    Попытка
        // Пытаемся захватить файл, переименовав его
        ПереместитьФайл(ПутьКФайлу, ПутьВРаботе);
    Исключение
        // Если файл уже переименован другим сеансом, сюда попадет ошибка
        Возврат; 
    КонецПопытки;
    
    // Работаем с файлом ПутьВРаботе
    ОбработатьДанныеИзФайла(ПутьВРаботе);
    
    // После обработки удаляем или перемещаем в архив
    УдалитьФайлы(ПутьВРаботе);
КонецЕсли;

Решение 4: Контроль через Регистр Сведений

Проанализируем еще один надежный метод — использование вспомогательного Регистра сведений (например, «ИсторияЗагрузкиЗаказов»).

Создадим регистр с одним измерением ИдентификаторЗаказа (тип, соответствующий вашему ИД из XML). При попытке создать заказ мы должны сначала записать этот ИД в регистр. Если в регистре уже есть такая запись, платформа выдаст ошибку уникальности ключа записи. Это произойдет даже внутри транзакции и на уровне СУБД, что является «железобетонной» защитой от дублей.

Как отлаживать такие ситуации?

Если проблема повторяется, выполним следующие шаги по диагностике:

  1. Журнал регистрации: Включите максимальную детализацию. Проверьте события «Сеанс. Начало». Если вы видите два сеанса под одним пользователем с разницей в доли секунды — это ваш случай.
  2. Технологический журнал: Настройте сбор событий TLOCK (блокировки) и TCMN (управление транзакциями). Это поможет увидеть, на каком этапе один процесс должен был заблокировать другой.
  3. Собственное логирование: Добавьте в код запись в текстовый файл с указанием НомерСеанса (через ПолучитьТекущийСеансИнформационнойБазы().НомерСеанса). Это позволит точно сопоставить записи в вашем логе с конкретными сессиями в 1С.

Подводя итог, можно сказать, что наиболее стабильным решением является сочетание Регламентного задания 1С и Управляемых блокировок внутри кода. Это гарантирует, что даже при случайном параллельном запуске система корректно выстроит процессы в очередь и не допустит создания дублирующих документов ЗаказКлиента.

← На главную