Как предотвратить появление дублей при параллельной записи справочника в 1С на версионных СУБД?

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

При вызове веб-сервисов или высокой активности пользователей в платформе 1С:Предприятие 8.3 часто возникает ситуация, когда несколько параллельных сеансов одновременно пытаются создать или обновить элемент справочника с одинаковыми уникальными реквизитами (например, ИНН и КПП). Если проверка уникальности выполняется через запрос в событии ПриЗаписи или в методе веб-сервиса без явной блокировки, система создаст дублирующие записи.

Рассмотрим, почему возникает эта проблема на версионных СУБД (в частности, PostgreSQL) и проанализируем правильные механизмы защиты от дублей в режиме управляемых блокировок.

Причина возникновения дублей при параллельной записи

Разберем по шагам, что происходит в СУБД (PostgreSQL) при работе двух параллельных транзакций в режиме изоляции Read Committed:

  1. Транзакция №1 начинает создание нового контрагента. Выполняется запрос на поиск существующей записи по ИНН и КПП. Так как запись еще не создана, запрос возвращает пустой результат.
  2. Транзакция №2 одновременно начинает создание контрагента с теми же ИНН и КПП. Выполняется аналогичный проверочный запрос. Так как Транзакция №1 еще не завершила запись и не зафиксировала транзакцию (COMMIT), версионная СУБД возвращает для Транзакции №2 снимок данных до начала первой транзакции. Результат запроса снова пуст.
  3. Обе транзакции считают, что дублей в системе нет, и успешно записывают элементы. В итоге в справочнике появляются два контрагента с одинаковыми ИНН и КПП.

Обычный запрос 1С (конструкция Запрос.Выполнить()) не накладывает блокировок СУБД на проверяемые данные и не проверяет управляемые блокировки 1С, если сеанс явно не установил их до выполнения запроса. Автоматическая управляемая блокировка платформы при записи нового элемента устанавливается только по ссылке самого объекта, которая для нового элемента всегда уникальна и никак не предотвратит совпадение ИНН и КПП.

Правильное решение: Явная управляемая блокировка данных

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

Проанализируем корректную последовательность действий при записи данных (в веб-сервисе или перед вызовом метода Записать()):

  1. Открываем транзакцию (НачатьТранзакцию()).
  2. Создаем объект БлокировкаДанных и устанавливаем исключительный режим блокировки по проверяемым полям (ИНН и КПП).
  3. Вызываем метод Блокировать(). Если другой сеанс уже блокирует эти же ИНН и КПП, текущий поток приостановит работу и будет ждать завершения той транзакции.
  4. Выполняем запрос на поиск существующего элемента в базе. К этому моменту незафиксированные данные других потоков либо уже запишутся и будут видны запросу, либо блокировка гарантирует отсутствие параллельной записи.
  5. Если объект найден — обновляем его или возвращаем отказ; если не найден — создаем новый.
  6. Записываем объект и фиксируем транзакцию (ЗафиксироватьТранзакцию()).

Рассмотрим пример кода 1С, реализующий данный алгоритм:


НачатьТранзакцию();

Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Контрагенты");
ЭлементБлокировки.УстановитьЗначение("ИНН", ВходнойИНН);
ЭлементБлокировки.УстановитьЗначение("КПП", ВходнойКПП);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ Первые 1
    |   Контрагенты.Ссылка КАК Ссылка
    |ИЗ
    |   Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |   Контрагенты.ИНН = &ИНН
    |   И Контрагенты.КПП = &КПП";

Запрос.УстановитьПараметр("ИНН", ВходнойИНН);
Запрос.УстановитьПараметр("КПП", ВходнойКПП);

Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда
    ОбъектКонтрагент = Выборка.Ссылка.ПолучитьОбъект();
Иначе
    ОбъектКонтрагент = Справочники.Контрагенты.СоздатьЭлемент();
    ОбъектКонтрагент.ИНН = ВходнойИНН;
    ОбъектКонтрагент.КПП = ВходнойКПП;
КонецЕсли;

// Заполнение остальных реквизитов
ОбъектКонтрагент.Наименование = ВходноеНаименование;
ОбъектКонтрагент.Записать();

ЗафиксироватьТранзакцию();

Важное условие: Для эффективной работы управляемой блокировки реквизиты ИНН и КПП в Конфигураторе должны быть внесены в свойство справочника Поля блокировки данных, а у самих реквизитов должно быть включено свойство Индексировать.

Распространенные ошибки при реализации

Ошибка 1: Чтение перед блокировкой ("Оптимистичная проверка")
Иногда разработчики пытаются сначала выполнить запрос на чтение без блокировки, и только если запись не найдена — ставить блокировку и создавать объект. Это критическая ошибка логики. В промежутке времени между выполнением проверочного запроса и вызовом метода Заблокировать() другой сеанс успеет выполнить свой запрос и также начнет процедуру записи. Управляемая блокировка должна накладываться строго до первого обращения к базе данных за проверкой дублей.

Ошибка 2: Создание уникальных индексов напрямую в СУБД
Попытки создать уникальный индекс средствами PostgreSQL напрямую в базе данных приводят к нарушению структуры хранения 1С. При очередном обновлении конфигурации или реструктуризации таблиц платформа 1С не сможет обработать сторонний индекс СУБД и завершит операцию аварийной ошибкой.

Альтернативный подход: Использование регистра сведений

Если проверка уникальности требуется по сложной комбинации полей, отличным решением является использование подчиненного или независимого регистра сведений (например, УникальностьКонтрагентов):

  1. Измерениями регистра выступают ИНН и КПП, а ресурсом или реквизитом — Контрагент.
  2. При попытке записи набора записей регистра сведений платформа 1С автоматически накладывает управляемую блокировку по значениям измерений и проверяет уникальность комбинации ключа записи.
  3. При параллельной попытке записать такой же набор записей второй поток получит штатное исключение платформы о нарушении уникальности записи регистра.
← На главную