Итак, для тестирования мы создадим пустую конфигурацию 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 сеансе нам не мешает его читать:
В этом и есть мораль snapshot-a: MS SQL работает в read commited snapshot в режиме версионирования записей - за пределами нашей изменяющей транзакции данные читаются из snapshot-а базы, и наши изменения данных внутри транзакции не вызывают блокировку на чтение этих данных вне этой транзакции.
Остаётся проверить: а как 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".
Таким образом, хотя таблица 3.6.2 на стр.41 книги Е.В.Филиппова "Настольная книга 1С:Эксперта по технологическим вопросам" и сообщает нам, что PostgreSQL в режиме управляемых блокировок использует уровень изоляции read commited - этот уровень изоляции в PostgreSQL соответсвует по поведению уровню read commited snapshot в MS SQL.