Чтобы “пощупать”, как ведёт себя система с разными настройками, я сделал элементарный пример с одной единственной таблицей – результаты экспериментов описаны ниже. Дополнительно выяснилось, что система ведёт себя по-разному не только с разными настройками, но и с одинаковыми настройками под разными СУБД (Postgre и MS SQL).
Итак, для тестирования мы создадим пустую конфигурацию 1С на платформе 8.3 (я использовал 8.3.8.1747), в режиме толстого клиента, с одним единственным справочником ДолжностиОрганизаций. В этот справочник добавим единственный элемент с кодом “ТестЭл” и наименованием “!Тестовый элемент”.
Для тестирования мы будем из двух параллельных сеансов читать, а затем попытаемся записать эту единственную запись, и будем наблюдать блокировки, возникающие при конкурентном обращении к одному и тому же ресурсу.
Делать это будем с помощь внешней обработки с простым кодом:
НачатьТранзакцию(); ТестЭлСсылка = Справочники.ДолжностиОрганизаций.НайтиПоКоду("ТестЭл"); Предупреждение("Прочитан: "+ТестЭлСсылка.Наименование); ТестЭлОбъект = ТестЭлСсылка.ПолучитьОбъект(); ТестЭлОбъект.Наименование = "!Тестовый элемент "+ТекущаяДата(); ТестЭлОбъект.Записать(); Если Вопрос("Записать? "+ТестЭлОбъект.Наименование,РежимДиалогаВопрос.ДаНет) = КодВозвратаДиалога.Да Тогда ЗафиксироватьТранзакцию(); Иначе ОтменитьТранзакцию(); КонецЕсли;
Для начала конфигурация под MS SQL у нас находится в режиме автоматических блокировок, что соответствует уровню изоляции repeatable read (повторяемое чтение) для Справочников:
Запускаем параллельно два сеанса, в обоих сеансах запускаем код обработки и проходим этап чтения справочника:
При нажатии на ОК в любом из окон далее происходит попытка записать этот элемент внутри транзакции. Поскольку каждым из сеансов на этот элемент установлена блокировка на чтение (котороя сохраняется до конца транзакции) – при попытке записи этого элемента под MS SQL получаем ошибку таймаута:
После ошибки таймаута транзакция во 2 сеансе откатывается, в 1 сеансе мы можем выполнить запись. На скриншоте запись элемента справочника уже выполнена, но сама транзакция ещё не завершилась:
При этом во 2 сеансе мы можем открыть форму списка справочника и увидеть там изменённое наименование элемента справочника, которое изменено не завершённой ещё транзакцией.
Это и есть иллюстрация “грязного чтения” (dirty read). Отображение формы списка справочника в данном случае осуществляется без учёта завершённости транзакций. При этом если в 1 сеансе мы нажмём НЕТ (отменим транзакцию) – только после обновления списка во 2 сеансе Наименование нашего тестового элемента вернётся в нетронутое состояние:
Интересно отметить, что с указанными настройками 1С база под PostgreSQL ведёт себя совершенно по-другому. После нажатия ОК после чтения PostgreSQL никакой таймаут не возвращает, а начинает спокойно ждать, когда освободится элемент от блокировки на чтение, чтоб его записать. Первый раз когда я с этим столкнулся – подождал минут 15 и хотел уже снимать повисший сеанс. Но после того как мы нажмём ОК во втором сеансе – возникает ошибка на взаимоблокировке (deadlock):
Это происходит потому, что наши 1 и 2 сеанс начинают ждать друг друга – каждый на попытке записи ждёт другой сеанс, пока тот снимет блокировку на чтение. После того как второй сеанс был выбран жертвой взаимоблокировки – его транзакция отменяется, и в первом сеансе выполняется код записи элемента внутри транзакции, о чём нам свидетельствует следующее диалоговое окно:
“Грязного чтения” при отображении списка Справочника в случае с PostgreSQL не происходит. Пока транзакция не завершилась – в другом сеансе мы видим в списке не изменённое название элемента справочника:
На этом рассмотрение режима repeatble read (повторяемое чтение, “автоматические блокировки” в 1С) мы закончим. Мораль repeatble read – если мы что-то прочитали в транзакции – никто из других транзакций не сможет изменить эти записи, пока наша транзакция не завершится (даже если мы ничего в ней записывать и не будем).
Теперь перейдём к рассмотрению режима read commited в MS SQL.
Для этого переведём конфигурацию в режим управляемых блокировок. Для этого переведём режим управления блокировками в состояние Управляемый и останемся в режиме совместимости с 8.2:
После применения изменений запустим в двух сеансах ту же обработку. Теперь при чтении элемента блокировка снимается после прочтения – и поэтому в одном из сеансов внутри транзакции мы можем осуществить запись элемента:
При попытке записать тот же элемент во втором сеансе получим ошибку-таймаут:
Закрыв окно с таймаутом – можем наблюдать грязное чтение изменённого элемента в незавершённой транзакции:
Это происходит потому что при выводе формы справочника данные читаются 1Сом вне транзакции, без учёта установленных блокировок. Если же мы попытаемся во 2 сеансе снова запустить обработку с начала – на этот раз получим таймаут уже на операции чтения:
Поскольку при записи элемента на него была установлена эксклюзивная блокировка, а транзакция в 1 сеансе ещё не завершилась.
Собственно, мораль read commited, в нашем случае: чтение данных в транзакции НЕ блокирует их автоматически от записи. Для принудительной блокировки нужно использовать объект БлокировкаДанных.
Ну и теперь переведём базу SQL в режим read commited snaphot, и попробуем понять, есть ли отличия работы в этом режиме? Режим совместимости базы установим в “Не использовать”:
При попытке параллельной записи элемента справочника во 2 сеансе мы также получаем таймаут, только его возвращает теперь не СУБД MS SQL, а сама платформа 1С:
Но теперь у нас исчезла проблема “грязного чтения” формы списка справочника:
И также мы можем повторно читать Наименование справочника во 2 сеансе в транзакции – читается Наименование, которое было до изменения, транзакция которая пишет это Наименование в 1 сеансе нам не мешает его читать:
Остаётся проверить: а как read commited работает на PostgreSQL?
При проверке оказывается, что под Postgre SQL база и в режиме совместимости с 8.2.13, и с отключенным режимом совместимости работает так, как MS SQL в режиме read commited snapshot, а именно:
Грязного чтения в форме списка справочника элемента, изменённого не завершённой транзакцией, не возникает.
Повторное чтение изменяемого внутри другой транзакции элемента – возможно.
Блокировка элемента, изменяемого внутри транзакции и выдача ошибки таймаута осуществляется на уровне платформы 1С:Предприятия, а не СУБД.
И это не удивительно, судя по документации PostgreSQL – то, что в MS SQL называется read commited snapshot – в PostgreSQL называется просто read commited, и данные читаются из snapshot базы без учёта незавершённых транзакций всегда. В PostgreSQL просто нет “read commited БЕЗ snapshot”.
Автор статьи – Илья Евгеньевич Петров
г. Самара, 2016 г.
На какой момент фиксируется “снимок” snapshot-а? Например начали транзакцию 1, выполняем какие то действия, но данные еще не читали и не записывали. В это время транзакция 2 изменила наши данные и завершилась. Транзакция 1 читает данные. Какие это будут данные? Те которые были до начала транзакции 1 или те которые получились в результате работы транзакции 2?
Добрый день! Первый и второй скриншоты с настройками совместимости неправильные.
На первом должно быть:
– Режим управления блокировкой данных – Автоматический;
– Режим совместимости – 8.2.13.
На втором должно быть:
– Режим управления блокировкой данных – Управляемый;
– Режим совместимости – 8.2.13.
На третьем все правильно:
– Режим управления блокировкой данных – Управляемый;
– Режим совместимости – Не использовать.
За статью спасибо!
Доброго дня, Александр!
Спасибо, статью исправили.
Спасибо, очень познавательный материал!
Одно замечания, второй скрин с настройками совместимости явно не верный (он такой-же как и третий), я так понимаю, там должно быть “Управляемый” и “Версия 8.2.13”.
Александр, спасибо!
Скриншот исправили.
Добрый день! Каким образом можно гарантированно избежать грязного чтения при формировании, например, отчета в 1С без использования read commited snaphot на SQL-сервере? Получается, что читать данные запросом, чтобы они всегда были “чистыми”, можно только в рамках транзакции, даже если в рамках этой транзакции мы сами ничего в базу не записываем?
Доброго дня, Мария!
Никак. Если транзакицю не начинать – будет грязное чтение (например, в консоли запросов). А если начинать транзакцию – будет таймаут, пока чужие блокировки не снимутся.