Вопрос параллельной работы пользователей в многопользовательской среде — один из самых важных для разработчика 1С. Часто возникает ситуация, когда две транзакции одновременно пытаются получить доступ к одним и тем же данным. Разберем детально, как платформа 1С:Предприятие 8 обрабатывает такие ситуации, особенно в режиме управляемых блокировок, и выясним, когда чтение совместимо с записью, а когда — нет.
Прежде всего, проанализируем разницу в подходах к блокировкам. В автоматическом режиме (который был основным в ранних версиях платформы 8.x) 1С полностью полагается на механизмы СУБД. При этом используется уровень изоляции Serializable или Repeatable Read. Это означает, что любое чтение в транзакции накладывает разделяемую блокировку на уровне базы данных, которая удерживается до конца транзакции. В результате, если одна транзакция прочитала данные, вторая не сможет их изменить, пока первая не завершится. Это обеспечивает высокую целостность, но сильно ограничивает параллельность.
В управляемом режиме ситуация меняется кардинально. Здесь за параллельность отвечают два уровня: менеджер управляемых блокировок 1С и механизмы СУБД. На уровне СУБД используется более «мягкий» уровень изоляции — Read Committed. Рассмотрим подробнее, к чему это приводит.
Отвечая на основной вопрос: да, операции обычного чтения из двух разных сеансов в управляемом режиме полностью совместимы. Это справедливо и для автоматического режима, если только в запросе не используется явное указание блокировки.
Выясним причину такой совместимости:
ВЫБРАТЬ... в управляемом режиме не накладывает никаких управляемых блокировок в 1С (если они не прописаны в коде явно).Рассмотрим более сложный сценарий: первая транзакция читает данные, а вторая пытается их записать. Сможет ли вторая транзакция выполнить запись, пока первая еще не завершена? В управляемом режиме — да, сможет, но с оговорками на тип используемой СУБД.
Проанализируем работу популярных систем:
READ_COMMITTED_SNAPSHOT (RCSI), чтение не ставит блокировок в СУБД. Оно читает «снимок» данных из tempdb. Запись в это время может идти своим чередом, так как она не конфликтует с чтением из снимка.Выяснив, что чтение и запись совместимы, мы сталкиваемся с опасностью. Рассмотрим пример: Транзакция №1 читает остаток товара (равен 10). Транзакция №2 читает тот же остаток (тоже видит 10). Транзакция №1 вычитает 5 единиц и записывает 5. Транзакция №2 вычитает 3 единицы и записывает 7 (потому что она видела 10). В итоге вместо корректного остатка 2 (10 - 5 - 3) мы получим 7. Это и есть классическое потерянное обновление.
Для предотвращения таких ситуаций в управляемом режиме 1С разработчик обязан использовать объект БлокировкаДанных. Разберем по шагам, как это работает:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
ЭлементБлокировки.УстановитьЗначение("Номенклатура", ВыбраннаяНоменклатура);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Только после этого выполняем запрос на чтение остатков
Запрос = Новый Запрос("ВЫБРАТЬ... ИЗ РегистрНакопления.ОстаткиТоваров...");
Важный момент: Управляемую блокировку нужно ставить до чтения данных. Исключительная блокировка в 1С несовместима ни с какой другой блокировкой на те же данные. Таким образом, вторая транзакция «встанет в очередь» на уровне сервера 1С, не доходя до СУБД, пока первая транзакция не завершится.
Существует альтернативный способ установить исключительную блокировку непосредственно в тексте запроса. Проанализируем пример использования конструкции ДЛЯ ИЗМЕНЕНИЯ:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки( , Номенклатура = &Ном) КАК Остатки
|ДЛЯ ИЗМЕНЕНИЯ";
При выполнении такого запроса внутри транзакции платформа автоматически установит исключительную управляемую блокировку на те записи, которые попадают в выборку. Это удобный механизм, позволяющий совместить чтение и защиту данных от изменения другими сеансами в один шаг.
Несмотря на то, что управляемый режим призван повысить параллельность, он не избавляет от риска Deadlock. Рассмотрим ситуацию:
Возникает классический тупик на уровне менеджера блокировок 1С. Чтобы этого избежать, всегда следуйте правилу: если в транзакции планируется запись данных, читайте их сразу с Исключительной блокировкой. Не используйте режим Разделяемая там, где за чтением последует модификация этих же данных.
Подведем итог нашему анализу:
Для обеспечения надежности используйте БлокировкаДанных или ДЛЯ ИЗМЕНЕНИЯ, если ваши расчеты зависят от прочитанных значений.