При изучении механизмов блокировок в 1С разработчики часто сталкиваются с разделом документации на ИТС, посвященным уровню изоляции READ COMMITTED. Описание этого уровня нередко вызывает вопросы, особенно в части утверждений о «решении проблемы неповторяемого чтения» и возможности чтения данных «не дожидаясь окончания транзакции». Разберем подробно, как работает этот механизм в современных СУБД, где документация может вводить в заблуждение и как 1С дополняет возможности СУБД собственным менеджером блокировок.
Для начала вспомним классическое определение: уровень изоляции READ COMMITTED (чтение зафиксированных данных) гарантирует, что транзакция может прочитать только те данные, изменения по которым уже были зафиксированы (выполнен COMMIT). Это базовый уровень для большинства современных СУБД, таких как MS SQL Server и PostgreSQL. Его основная задача — предотвращение «грязного чтения» (Dirty Read).
Грязное чтение — это ситуация, когда одна транзакция читает данные, которые были изменены другой транзакцией, но еще не зафиксированы. Если вторая транзакция выполнит ROLLBACK (откат), первая транзакция окажется в ситуации, когда она обработала данные, которых на самом деле не существует.
Рассмотрим подробнее формулировку из ИТС: «Если моя транзакция начала изменять данные, то конкурирующая транзакция не может не только изменить, но даже прочитать их до завершения моих изменений». Здесь кроется первая ловушка. В классической блокировочной модели (Pessimistic Concurrency) пишущая транзакция накладывает эксклюзивную блокировку (X-lock), которая действительно запрещает чтение другим транзакциям, работающим на уровне READ COMMITTED. Однако в современных реалиях 1С всё немного иначе.
Проанализируем ситуацию с точки зрения современных версий платформы 1С 8.3. Начиная с версии 8.3.3, при использовании MS SQL Server 1С по умолчанию включает режим Read Committed Snapshot Isolation (RCSI). В этом режиме поведение СУБД меняется кардинально:
TempDB, где хранится старая версия данных, актуальная на момент начала выполнения запроса SELECT.Именно поэтому фраза в ИТС «могут читать данные, не дожидаясь окончания моей транзакции в целом» технически верна для версионных СУБД (PostgreSQL) или MS SQL с включенным RCSI. Читающая транзакция видит «закоммиченное» состояние на момент своего старта, игнорируя текущие незавершенные изменения.
Выясним причину споров вокруг фразы ИТС: «Таким образом решается проблема неповторяемого чтения». В академической теории баз данных это утверждение считается ошибочным. Уровень READ COMMITTED по определению не защищает от неповторяемого чтения.
Неповторяемое чтение (Non-repeatable read) — это ситуация, когда внутри одной транзакции вы дважды выполняете один и тот же запрос на чтение, но получаете разные результаты, потому что между этими запросами другая транзакция изменила данные и зафиксировала их (COMMIT).
Разберем это на примере:
COMMIT.В рамках Транзакции №1 данные изменились «под ногами», что и называется неповторяемым чтением. Уровень READ COMMITTED это допускает, так как Транзакция №2 уже зафиксирована.
Почему же в ИТС написано, что проблема решается? Вероятно, авторы имели в виду, что платформа 1С в режиме управляемых блокировок перекладывает ответственность за целостность данных на плечи разработчика и Менеджер управляемых блокировок.
Поскольку сама СУБД на уровне READ COMMITTED позволяет данным меняться в процессе нашей транзакции, платформа 1С использует механизм управляемых блокировок. Рассмотрим, как правильно реализовать контроль остатков, чтобы избежать проблем.
Проанализируем классический код проведения документа:
// Создаем объект блокировки
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
ЭлементБлокировки.УстановитьЗначение("Номенклатура", ВыбНоменклатура);
// Устанавливаем исключительную блокировку
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Теперь выполняем запрос к остаткам
Запрос = Новый Запрос("ВЫБРАТЬ КоличествоОстаток ИЗ РегистрНакопления.ОстаткиНоменклатуры.Остатки(...)");
// ... проверка и запись движений
Что здесь происходит:
Блокировка.Заблокировать() создает запись в памяти сервера 1С.ОбработкаПроведения).Таким образом, именно связка READ COMMITTED в СУБД + Исключительная управляемая блокировка в 1С фактически эмулирует уровень изоляции REPEATABLE READ или даже SERIALIZABLE для конкретных данных, решая проблему неповторяемого чтения.
Если же мы работаем в старой конфигурации без управляемых блокировок (автоматический режим), то MS SQL использует классические S-блокировки (Shared). Посмотрим на важный нюанс: на уровне READ COMMITTED СУБД снимает разделяемую блокировку сразу после того, как данные прочитаны, не дожидаясь завершения транзакции.
Это подтверждает тезис из обсуждения: «данные, блокируемые читающей транзакцией, освобождаются сразу после завершения операции SELECT». Это сделано для повышения параллельности, но именно это и делает неповторяемое чтение возможным в «чистой» СУБД без дополнительных механизмов 1С.
Подводя итог анализу уровня изоляции READ COMMITTED, можно сделать следующие выводы:
READ COMMITTED не решает проблему неповторяемого чтения. Это подтверждается как теорией БД, так и другими методическими материалами фирмы 1С (например, книгой «Профессиональная разработка в системе 1С:Предприятие»).БлокировкаДанных с режимом Исключительный.READ COMMITTED является оптимальным балансом между скоростью (параллельностью) и целостностью, если разработчик грамотно использует управляемые блокировки.Помните, что безопасность ваших данных в транзакции при уровне READ COMMITTED — это не свойство СУБД, а результат вашей работы с менеджером управляемых блокировок 1С.