Совместимы ли операции чтения и записи из разных сеансов в транзакции 1С

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

Вопрос параллельной работы пользователей в многопользовательской среде — один из самых важных для разработчика 1С. Часто возникает ситуация, когда две транзакции одновременно пытаются получить доступ к одним и тем же данным. Разберем детально, как платформа 1С:Предприятие 8 обрабатывает такие ситуации, особенно в режиме управляемых блокировок, и выясним, когда чтение совместимо с записью, а когда — нет.

Различие автоматического и управляемого режимов блокировок

Прежде всего, проанализируем разницу в подходах к блокировкам. В автоматическом режиме (который был основным в ранних версиях платформы 8.x) 1С полностью полагается на механизмы СУБД. При этом используется уровень изоляции Serializable или Repeatable Read. Это означает, что любое чтение в транзакции накладывает разделяемую блокировку на уровне базы данных, которая удерживается до конца транзакции. В результате, если одна транзакция прочитала данные, вторая не сможет их изменить, пока первая не завершится. Это обеспечивает высокую целостность, но сильно ограничивает параллельность.

В управляемом режиме ситуация меняется кардинально. Здесь за параллельность отвечают два уровня: менеджер управляемых блокировок 1С и механизмы СУБД. На уровне СУБД используется более «мягкий» уровень изоляции — Read Committed. Рассмотрим подробнее, к чему это приводит.

Совместимость чтений из разных сеансов

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

Выясним причину такой совместимости:

Совместимость чтения и записи: СУБД и MVCC

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

Проанализируем работу популярных систем:

  1. MS SQL Server: Если включен режим READ_COMMITTED_SNAPSHOT (RCSI), чтение не ставит блокировок в СУБД. Оно читает «снимок» данных из tempdb. Запись в это время может идти своим чередом, так как она не конфликтует с чтением из снимка.
  2. PostgreSQL и Oracle: Эти системы являются «версионниками» (MVCC) по своей архитектуре. Чтение и запись в них не блокируют друг друга по определению. Читающая транзакция всегда видит согласованное состояние данных на момент своего начала (или начала запроса).
  3. Файловая база данных: Здесь ситуация иная. В файловом режиме блокировка ставится на всю таблицу. Хотя два чтения совместимы, любая попытка записи заблокирует таблицу целиком, и другие сеансы не смогут даже прочитать данные до окончания записи.

Проблема «Потерянного обновления» (Lost Update)

Выяснив, что чтение и запись совместимы, мы сталкиваемся с опасностью. Рассмотрим пример: Транзакция №1 читает остаток товара (равен 10). Транзакция №2 читает тот же остаток (тоже видит 10). Транзакция №1 вычитает 5 единиц и записывает 5. Транзакция №2 вычитает 3 единицы и записывает 7 (потому что она видела 10). В итоге вместо корректного остатка 2 (10 - 5 - 3) мы получим 7. Это и есть классическое потерянное обновление.

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


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

// Только после этого выполняем запрос на чтение остатков
Запрос = Новый Запрос("ВЫБРАТЬ... ИЗ РегистрНакопления.ОстаткиТоваров...");

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

Использование ключевого слова «ДЛЯ ИЗМЕНЕНИЯ»

Существует альтернативный способ установить исключительную блокировку непосредственно в тексте запроса. Проанализируем пример использования конструкции ДЛЯ ИЗМЕНЕНИЯ:


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

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

Взаимоблокировки (Deadlocks) в управляемом режиме

Несмотря на то, что управляемый режим призван повысить параллельность, он не избавляет от риска Deadlock. Рассмотрим ситуацию:

  1. Транзакция А ставит Разделяемую блокировку на Объект 1.
  2. Транзакция Б ставит Разделяемую блокировку на Объект 1 (они совместимы, обе проходят).
  3. Транзакция А пытается «повысить» блокировку до Исключительной для записи. Она ждет Транзакцию Б.
  4. Транзакция Б также пытается повысить свою блокировку до Исключительной. Она ждет Транзакцию А.

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

Итоги

Подведем итог нашему анализу:

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

← На главную