При вызове веб-сервисов или высокой активности пользователей в платформе 1С:Предприятие 8.3 часто возникает ситуация, когда несколько параллельных сеансов одновременно пытаются создать или обновить элемент справочника с одинаковыми уникальными реквизитами (например, ИНН и КПП). Если проверка уникальности выполняется через запрос в событии ПриЗаписи или в методе веб-сервиса без явной блокировки, система создаст дублирующие записи.
Рассмотрим, почему возникает эта проблема на версионных СУБД (в частности, PostgreSQL) и проанализируем правильные механизмы защиты от дублей в режиме управляемых блокировок.
Разберем по шагам, что происходит в СУБД (PostgreSQL) при работе двух параллельных транзакций в режиме изоляции Read Committed:
COMMIT), версионная СУБД возвращает для Транзакции №2 снимок данных до начала первой транзакции. Результат запроса снова пуст.Обычный запрос 1С (конструкция Запрос.Выполнить()) не накладывает блокировок СУБД на проверяемые данные и не проверяет управляемые блокировки 1С, если сеанс явно не установил их до выполнения запроса. Автоматическая управляемая блокировка платформы при записи нового элемента устанавливается только по ссылке самого объекта, которая для нового элемента всегда уникальна и никак не предотвратит совпадение ИНН и КПП.
Для гарантированной защиты от гонки состояний необходимо явно накладывать управляемую блокировку 1С по ключевым реквизитам до выполнения проверочного запроса. Управляемая блокировка в 1С работает кооперативно: если параллельный поток пытается установить исключительную блокировку на тот же набор реквизитов, он переходит в состояние ожидания до завершения первой транзакции.
Проанализируем корректную последовательность действий при записи данных (в веб-сервисе или перед вызовом метода Записать()):
НачатьТранзакцию()).БлокировкаДанных и устанавливаем исключительный режим блокировки по проверяемым полям (ИНН и КПП).Блокировать(). Если другой сеанс уже блокирует эти же ИНН и КПП, текущий поток приостановит работу и будет ждать завершения той транзакции.ЗафиксироватьТранзакцию()).Рассмотрим пример кода 1С, реализующий данный алгоритм:
НачатьТранзакцию();
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Контрагенты");
ЭлементБлокировки.УстановитьЗначение("ИНН", ВходнойИНН);
ЭлементБлокировки.УстановитьЗначение("КПП", ВходнойКПП);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Первые 1
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И Контрагенты.КПП = &КПП";
Запрос.УстановитьПараметр("ИНН", ВходнойИНН);
Запрос.УстановитьПараметр("КПП", ВходнойКПП);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ОбъектКонтрагент = Выборка.Ссылка.ПолучитьОбъект();
Иначе
ОбъектКонтрагент = Справочники.Контрагенты.СоздатьЭлемент();
ОбъектКонтрагент.ИНН = ВходнойИНН;
ОбъектКонтрагент.КПП = ВходнойКПП;
КонецЕсли;
// Заполнение остальных реквизитов
ОбъектКонтрагент.Наименование = ВходноеНаименование;
ОбъектКонтрагент.Записать();
ЗафиксироватьТранзакцию();
Важное условие: Для эффективной работы управляемой блокировки реквизиты ИНН и КПП в Конфигураторе должны быть внесены в свойство справочника Поля блокировки данных, а у самих реквизитов должно быть включено свойство Индексировать.
Ошибка 1: Чтение перед блокировкой ("Оптимистичная проверка")
Иногда разработчики пытаются сначала выполнить запрос на чтение без блокировки, и только если запись не найдена — ставить блокировку и создавать объект. Это критическая ошибка логики. В промежутке времени между выполнением проверочного запроса и вызовом метода Заблокировать() другой сеанс успеет выполнить свой запрос и также начнет процедуру записи. Управляемая блокировка должна накладываться строго до первого обращения к базе данных за проверкой дублей.
Ошибка 2: Создание уникальных индексов напрямую в СУБД
Попытки создать уникальный индекс средствами PostgreSQL напрямую в базе данных приводят к нарушению структуры хранения 1С. При очередном обновлении конфигурации или реструктуризации таблиц платформа 1С не сможет обработать сторонний индекс СУБД и завершит операцию аварийной ошибкой.
Если проверка уникальности требуется по сложной комбинации полей, отличным решением является использование подчиненного или независимого регистра сведений (например, УникальностьКонтрагентов):
ИНН и КПП, а ресурсом или реквизитом — Контрагент.