Как правильно установить блокировку чтения регистра накопления в управляемом режиме, если у регистра стоит режим «Автоматический»?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В практике разработки высоконагруженных систем на платформе 1С:Предприятие, таких как «1С:Управление торговлей 11» (УТ 11), часто возникают задачи, требующие монопольного доступа к данным на короткий промежуток времени. Классический пример — подбор серий товаров с минимальным сроком годности (FEFO) или резервирование остатков под заказ.

Мы часто сталкиваемся с заблуждением относительно приоритетов режимов блокировок. Давайте разберем ситуацию, когда нам нужно исключить параллельное чтение и изменение остатков, при этом у самого регистра в свойствах стоит режим Автоматический, а вся конфигурация работает в режиме Управляемый. Как действовать правильно: менять режим регистра, использовать SQL-конструкции или применять штатные механизмы платформы?

Суть проблемы: конфликт режимов и ожиданий

Представим задачу: система получает файлы от клиента, на основании которых нужно сформировать заказы. Критически важно выбрать серии товаров с истекающим сроком годности. Если два менеджера или два фоновых задания одновременно начнут обрабатывать заказы с одной и той же номенклатурой, может возникнуть состояние «гонки» (race condition). Оба процесса прочитают одни и те же остатки, зарезервируют одну и ту же серию, и при записи возникнет ошибка (в лучшем случае) или пересорт/минус на складе (в худшем).

Разработчик видит, что у регистра ТоварыНаСкладе свойство «Режим управления блокировкой данных» установлено в значение Автоматический. При этом известно, что современные конфигурации (ERP, УТ 11, КА) работают в режиме Управляемый на уровне корня конфигурации.

Возникают вопросы:

  1. Нужно ли менять режим регистра на Управляемый?
  2. Можно ли использовать в запросе конструкцию ДЛЯ ИЗМЕНЕНИЯ (FOR UPDATE)?
  3. Как гарантировать, что никто не «уведет» товар из-под носа текущей транзакции?

Давайте последовательно ответим на эти вопросы и выработаем верное решение.

Анализ приоритетов блокировок: Конфигурация vs Объект

Первое, что нам необходимо усвоить: главным является режим управления блокировками, установленный для всей Конфигурации.

Если в корне конфигурации свойство «Режим управления блокировкой данных» установлено в значение Управляемый, то платформа переводит работу с СУБД (SQL Server, PostgreSQL) в режим изоляции транзакций, который не накладывает блокировок при обычном чтении (обычно это READ COMMITTED или SNAPSHOT).

В этом случае свойство конкретного объекта (справочника, регистра), установленное в Автоматический, фактически игнорируется платформой в контексте автоматической трансляции в SQL-блокировки. Платформа ожидает, что разработчик сам возьмет на себя ответственность за логическую целостность данных с помощью менеджера транзакционных блокировок.

Вывод: Менять режим регистра ТоварыНаСкладе нет никакого смысла и даже вредно (это затруднит обновление типовой конфигурации). Нужно использовать инструменты управляемых блокировок.

Почему ДЛЯ ИЗМЕНЕНИЯ — это плохая идея?

Часто возникает соблазн использовать конструкцию языка запросов ДЛЯ ИЗМЕНЕНИЯ. Давайте разберем, почему в нашем случае это может быть ошибкой.

Конструкция ДЛЯ ИЗМЕНЕНИЯ транслируется в SQL-инструкцию FOR UPDATE (или аналоги). Это накладывает прямую блокировку на записи в таблице СУБД. Однако, менеджер управляемых блокировок 1С (объект БлокировкаДанных) «не знает» об этой блокировке.

Это создает риск классической взаимоблокировки (deadlock) между двумя транзакциями:

Поэтому в конфигурациях с управляемым режимом рекомендуется использовать исключительно объект БлокировкаДанных, не смешивая его с прямыми указаниями СУБД в запросах.

Правильное решение: использование объекта БлокировкаДанных

Чтобы решить задачу корректно, мы должны использовать явную установку управляемой блокировки. Рассмотрим этот процесс по шагам.

Шаг 1. Обязательное использование транзакции

Любые блокировки имеют смысл и живут только в рамках транзакции. Как только транзакция завершается (фиксируется или отменяется), блокировка снимается. Поэтому наш код должен быть обернут в транзакцию.

Шаг 2. Определение пространства блокировки

В управляемом режиме мы блокируем не «таблицу целиком», а конкретный набор измерений. Это повышает параллельность работы. Если мы подбираем товар «Стол», нам не нужно блокировать работу с товаром «Стул».

Для регистра ТоварыНаСкладе ключевыми измерениями обычно являются:

Шаг 3. Реализация в коде

Давайте напишем примерный алгоритм решения задачи автора. Мы сначала подготовим данные для блокировки (список товаров из файла), затем откроем транзакцию, заблокируем конкретные товары и только потом прочитаем остатки.


// Предположим, у нас есть ТаблицаИзФайла с колонками Номенклатура, Склад
// 1. Сначала подготовим данные, чтобы минимизировать время внутри транзакции

// 2. Начинаем транзакцию
НачатьТранзакцию();

Попытка
    // 3. Создаем объект блокировки
    Блокировка = Новый БлокировкаДанных;
    
    // Указываем, какой регистр будем блокировать
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладе");
    
    // 4. Устанавливаем режим блокировки
    // "Исключительный" - запрещает другим и читать (с намерением блокировки), и писать.
    // Это именно то, что нужно для предотвращения параллельного распределения.
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    
    // 5. Устанавливаем отбор по измерениям из нашего источника данных
    // Это критически важно для производительности!
    // ИсточникДанных может быть ТаблицаЗначений, РезультатЗапроса и т.д.
    ЭлементБлокировки.ИсточникДанных = ТаблицаИзФайла; 
    
    // Сопоставляем поля источника с измерениями регистра
    ЭлементБлокировки.ИспользоватьИзИсточника("Номенклатура", "Номенклатура");
    ЭлементБлокировки.ИспользоватьИзИсточника("Склад", "Склад");
    
    // 6. Активируем блокировку
    // В этот момент система проверит, не заняты ли эти ресурсы другими транзакциями.
    // Если заняты - текущий поток встанет в ожидание.
    Блокировка.Заблокировать();
    
    // --- ЗОНА МОНОПОЛЬНОГО ДОСТУПА ---
    
    // 7. Выполняем запрос к остаткам
    // Теперь мы гарантированно читаем актуальные данные, которые никто не изменит
    // до конца нашей транзакции.
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТоварыНаСкладеОстатки.Серия КАК Серия,
        |   ТоварыНаСкладеОстатки.Номенклатура КАК Номенклатура,
        |   ТоварыНаСкладеОстатки.ВНаличииОстаток КАК Остаток,
        |   ТоварыНаСкладеОстатки.Серия.ГоденДо КАК СрокГодности
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладе.Остатки(
        |       ,
        |       (Номенклатура, Склад) В
        |           (ВЫБРАТЬ
        |               Т.Номенклатура,
        |               Т.Склад
        |           ИЗ
        |               &ТаблицаТоваров КАК Т)
        |   ) КАК ТоварыНаСкладеОстатки
        |
        |УПОРЯДОЧИТЬ ПО
        |   СрокГодности"; // Наш FEFO алгоритм
        
    Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаИзФайла);
    Результат = Запрос.Выполнить();
    
    // 8. Обработка результатов, распределение серий и создание документов/записей
    // ... код распределения ...
    
    // 9. Фиксация транзакции
    // Блокировка снимается автоматически
    ЗафиксироватьТранзакцию();
    
Исключение
    ОтменитьТранзакцию();
    // Логирование ошибки
    ЗаписьЖурналаРегистрации("ОбработкаФайлов", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
    ВызватьИсключение; // Пробрасываем ошибку дальше
КонецПопытки;

Важные нюансы и мифы

В процессе обсуждения темы мы затронули несколько важных аспектов, которые стоит проговорить отдельно.

Миф: «Я заблокирую данные, и другие не смогут их прочитать»

В управляемом режиме это не совсем так. Если вы установили Блокировка.Заблокировать(), вы запретили другим транзакциям:

  1. Записывать данные по этим измерениям.
  2. Устанавливать свои блокировки на эти же измерения.

Однако, если другой пользователь запустит отчет (простой SELECT к базе данных) без установки блокировок, он сможет прочитать данные ("грязное" чтение или чтение версии snapshot, в зависимости от СУБД). Но для нашей задачи распределения товара это не страшно — главное, что конкурирующий алгоритм проведения заказа тоже будет пытаться наложить блокировку и будет ждать, пока мы не закончим.

Стратегия «Свободные остатки»

В конфигурациях уровня УТ 11, помимо физических остатков (ТоварыНаСкладе), существуют регистры, отвечающие за резервы и доступность (например, СвободныеОстатки или вычисляемые механизмы обеспечения). Если ваша задача — просто зарезервировать товар, возможно, стоит использовать штатные методы менеджера обеспечения, а не писать прямые запросы к складским остаткам. Однако, если задача специфична (выбор конкретной серии по сроку годности из файла), подход с управляемой блокировкой, описанный выше, является единственно верным.

Резюме

  1. Игнорируйте режим «Автоматический» у регистра, если конфигурация в «Управляемом» режиме.
  2. Всегда используйте конструкцию НачатьТранзакцию() ... ЗафиксироватьТранзакцию().
  3. Используйте объект БлокировкаДанных для исключения параллельного доступа.
  4. Устанавливайте блокировку перед чтением данных в запросе.
  5. Обязательно указывайте измерения (Номенклатура, Склад) в блокировке, чтобы не остановить работу всего склада.
← На главную