Зачем оборачивать запрос в транзакцию в 1С, если нет записи данных?

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

При изучении типовых конфигураций или библиотек, таких как Библиотека Стандартных Подсистем (БСП), разработчики нередко сталкиваются с конструкцией, где выполнение обычного запроса на чтение данных заключено в явную транзакцию:


НачатьТранзакцию();
Попытка
    Запрос = Новый Запрос;
    // ... заполнение текста запроса и параметров
    РезультатыЗапроса = Запрос.ВыполнитьПакет();
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

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

Обеспечивает ли транзакция согласованность чтения по умолчанию?

Распространенное заблуждение разработчиков заключается в том, что сам по себе вызов метода НачатьТранзакцию() переводит сеанс в режим с полной изоляцией читаемых данных (например, Snapshot или Repeatable Read), гарантируя, что во время работы транзакции никакие параллельные процессы не изменят считываемые остатки или реквизиты.

Рассмотрим, как работают современные базы данных под управлением платформы 1С:Предприятие 8.3:

  1. Режим управляемых блокировок: В этом режиме СУБД (MS SQL Server, PostgreSQL и др.) работают на уровне изоляции Read Committed с механизмом версионирования строк (RCSI / MVCC). Это означает, что транзакция в 1С не блокирует таблицы на чтение и не обеспечивает повторяемость чтения для всей длительности транзакции автоматически.
  2. Границы согласованности: Каждый отдельный SQL-запрос видит данные, зафиксированные на момент старта именно этого SQL-запроса. Если внутри одной транзакции 1С последовательно выполнить два разных вызова Запрос.Выполнить(), второй запрос вполне может увидеть изменения, сделанные параллельным сеансом между первым и вторым вызовом.
  3. Пакетные запросы: Метод Запрос.ВыполнитьПакет() отправляет весь пакет запросов на сторону СУБД единым батчем. Сервер СУБД исполняет его в рамках единой внутренней команды, поэтому для обеспечения целостности между временными таблицами одного пакета открывать внешнюю транзакцию в коде 1С не требуется.
  4. Конструкция «ДЛЯ ИЗМЕНЕНИЯ»: Указание этой инструкции в тексте запроса предназначалось для автоматического режима блокировок, где накладывалась исключительная блокировка СУБД. В режиме управляемых блокировок данная синтаксическая конструкция игнорируется платформой.

Почему такой код появляется в типовых конфигурациях?

Проанализируем практические причины, по которым чтение без записи оборачивается в транзакцию:

  1. Удаленная или забытая управляемая блокировка: По правилам платформы объект БлокировкаДанных может накладывать блокировки только внутри активной транзакции (иначе генерируется исключительная ситуация: Блокировка данных может устанавливаться только в транзакции!). Нередко при рефакторинге модуля саму блокировку убирают или переносят, а открывающую и закрывающую транзакцию конструкции оставляют в коде.
  2. Неполная реализация стандарта «Ответственное чтение данных»: По стандартам разработки 1С, если на основании прочитанных данных принимаются ключевые бизнес-решения (например, резервирование остатков, контроль лимитов или выгрузка в стороннюю систему), чтение должно быть ответственным. Стандарт требует обязательного использования объекта БлокировкаДанных в явной транзакции. Если программист открыл транзакцию, но забыл заблокировать пространство данных, цель изоляции не достигается.
  3. Следствие рефакторинга модификации данных: Ранее в этом блоке могла выполняться запись (например, обновление регистра сведений, фиксация номера пакета или запись в служебную очередь), которую позже вынесли во внешний вызов, оставив запрос в пустой транзакционной обертке.

Побочные эффекты и риски пустых транзакций

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

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

Как правильно организовать чтение: варианты решения

Вариант 1. Простое чтение данных (транзакция не требуется)

Если прочитанные данные используются для отображения в отчете, форме или для фонового анализа без изменения базы, удалим транзакционные обертки:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ОстаткиТоваровОстатки.Номенклатура КАК Номенклатура,
    |   ОстаткиТоваровОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |   РегистрНакопления.ОстаткиТоваров.Остатки(&Период, ) КАК ОстаткиТоваровОстатки";

Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
РезультатЗапроса = Запрос.Выполнить();

Вариант 2. Ответственное чтение данных с управляемой блокировкой

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


НачатьТранзакцию();
Попытка
    // Создаем и настраиваем управляемую блокировку
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Разделяемый; // или Исключительный при подготовке к изменению
    ЭлементБлокировки.УстановитьЗначение("Склад", ВыбранныйСклад);
    Блокировка.Заблокировать();
    
    // Выполняем чтение заблокированных данных
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ОстаткиТоваровОстатки.Номенклатура КАК Номенклатура,
        |   ОстаткиТоваровОстатки.КоличествоОстаток КАК Количество
        |ИЗ
        |   РегистрНакопления.ОстаткиТоваров.Остатки(, Склад = &Склад) КАК ОстаткиТоваровОстатки";
        
    Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
    Результат = Запрос.Выполнить();
    
    // Здесь выполняются проверки или запись связанных данных
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Вариант 3. Передача промежуточных данных между несвязанными запросами

Если целью использования транзакции было сохранение временных таблиц между вызовами отдельных запросов, транзакция также не нужна. Для этого предназначен объект МенеджерВременныхТаблиц:


МенеджерВТ = Новый МенеджерВременныхТаблиц;

Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос1.Текст = 
    "ВЫБРАТЬ
    |   Ссылка КАК Ссылка
    |ПОМЕСТИТЬ ВТ_Документы
    |ИЗ
    |   Документ.РеализацияТоваровУслуг
    |ГДЕ
    |   Проведен = ИСТИНА";
Запрос1.Выполнить();

// Второй запрос использует созданную таблицу без открытия транзакции
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст = 
    "ВЫБРАТЬ
    |   Ссылка
    |ИЗ
    |   ВТ_Документы";
Результат = Запрос2.Выполнить();

МенеджерВТ.Закрыть();

Таким образом, наличие транзакции вокруг одиночного запроса на чтение без вызова метода Блокировка.Заблокировать() и без записи данных является избыточным кодом. В зависимости от требований к изоляции такой блок следует либо очистить от транзакционных методов, либо дополнить управляемой блокировкой по стандарту ответственного чтения.

← На главную