gifts2017

"На пальцах" - чем отличается "repeatable read" от "read commited" и "read commited snapshot"?

Опубликовал Илья Петров (ilya_petrov) в раздел Программирование - Практика программирования

В сети и в книгах довольно много информации с описанием уровней изоляции транзакций, их особенностей и отличий. Когда читаешь - всё вроде понятно, но при столкновении с практическими задачами возникают трудности. Чтобы "пощупать", как ведёт себя система с разными настройками, я сделал элементарный пример с одной единственной таблицей - результаты экспериментов описаны ниже. Дополнительно выяснилось, что система ведёт себя по-разному не только с разными настройками, но и с одинаковыми настройками под разными СУБД (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 сеансе нам не мешает его читать:

В этом и есть мораль 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.

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Виталий Попов (Сурикат) 10.11.16 14:11
А кто-нибудь исследовал чем отличается инструкция (взятая с сайта Гилева):
USE [master]
GO
ALTER DATABASE [Моя База] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
ALTER DATABASE [Моя База]
SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE [Моя База]
SET READ_COMMITTED_SNAPSHOT ON
ALTER DATABASE [Моя База] SET MULTI_USER WITH ROLLBACK IMMEDIATE
GO

От того, что есть по умолчанию в 1С в режиме совмести 8.3.6 и выше?
2. Максим Старков (max_st) 10.11.16 15:44
Пробовал, когда нужно было принудительно включить RCSI, но конфигурация была совместима только с 8.2. Работает.
3. Артём Андриянов (CSiER) 11.11.16 04:56
Несколько вопросов по статье:
Собственно, мораль read commited, в нашем случае: чтение данных в транзакции НЕ блокирует их автоматически от записи. Для принудительной блокировки нужно использовать объект БлокировкаДанных.
- разве без БлокировкаДанных это будет не READ UNCOMMITTED?
Для начала конфигурация под MS SQL у нас находится в режиме автоматических блокировок, что соответствует уровню изоляции repeatable read
и ниже
Это и есть иллюстрация "грязного чтения" (dirty read)
- то есть при repeatable read получили dirty read?
• Не увидел ссылку на обработку.
Таким образом, хотя таблица 3.6.2 на стр.41 книги Е.В.Филиппова "Настольная книга 1С:Эксперта по технологическим вопросам" и сообщает нам, что PostgreSQL в режиме управляемых блокировок использует уровень изоляции read commited - этот уровень изоляции в PostgreSQL соответсвует по поведению уровню read commited snapshot в MS SQL.
- книга хорошая, написано верно, ведь Postgre чистый версионник, а ms sql - блокировочник.
4. Максим Кузнецов (Makushimo) 11.11.16 06:06
(3) CSiER,
Поддержу вопрос коллеги.
Разве грязное чтение не исчезает на уровне изоляции READ COMMITED и выше?
5. Sergey Andreev (starik-2005) 11.11.16 12:38
В принципе то же самое рассказывают на курсах подготовки к эксперту/профессионалу по тех.вопросам. Единственное, что после курсов вопросы из комментов не возникают. ))
6. Анатолий Бритько (headMade) 11.11.16 12:39
(3) CSiER,
Надо разделять менеджер блокировок MSQL и менеджер блокировок 1С.
Когда у вас включен автоматический режим блокировок, то работает только менеджер блокировок MSQL, при этом используется уровень изоляции транзакций repeatable read и serializable.
Причем это не сам MSQL решает какой уровень изоляции использовать при получении данных, а это сервер 1С при передаче запроса на выполнение MSQL "говорит" какой уровень изоляции для какой таблицы необходимо установить. Т.е. тут за блокировку полностью отвечает MSQL.

Когда вы включаете управляемый режим блокировок, то начинает работать менеджер блокировок 1С. И соответственно сервер 1С решает что для MSQL будет достаточным уровень изоляции транзакций read commited т.к. за блокировку данных будет отвечать сам сервер 1С. В этом случае MSQL фактически ничего не блокирует.
А "БлокировкаДанных " - это фактически команда серверу 1С, что необходимо наложить блокировку на определенные данные.
Т.е. не взирая на то используем мы или нет "БлокировкаДанных ", на уровне MSQL всегда будет использоваться read commited.
7. Анатолий Бритько (headMade) 11.11.16 12:44
(3) CSiER,
то есть при repeatable read получили dirty read?


В статье же написано про 2 сеанса.
В первом сеансе у нас устанавливается блокировка с уровнем изоляции repeatable read.
А вот ВО ВТОРОМ сеансе у нас будет видно dirty read.

Т.к. в приведенном примере стоит уровень совместимости с 8.2, то read commited snaphot не используется. Соответственно когда пользователь во втором сеансе открывает форму списка, то фактически сервер 1С передает запрос MSQL с хинтом "with no lock". Что фактически означает что будут прочитаны все данные в т.ч. данные не зафиксированных транзакций. Поэтому именно ВО ВТОРОМ сеансе будет наблюдаться грязное чтение, невзирая на то что в первом сеансе на этих записях стоит блокировка с уровнем узоляции repeatable read.
8. Sergey Andreev (starik-2005) 11.11.16 14:17
(6) headMade, а вот г-н Филиппов на курсах, помнится, говорит о том, что блокировки MS SQL все-таки устанавливает. В режиме 8.2 - реад комиттед, в режиме 8.3 - реад комиттед снапшот. В профайлере при этом нет инструкции WITH (NOLOCK), но даже наличие WITH (NOLOCK) не гарантирует их отсутствие.
9. Сергей Носков (Sergey.Noskov) 11.11.16 14:34
(1) Сурикат, ничем не отличается, просто 8.3 это делает сама
10. Сан Саныч (herfis) 11.11.16 15:03
(8) Вы путаете блокировки с уровнями изоляции транзакций. И блокировки у MSSQL бывают разные, есть много вспомогательных. С практической точки зрения важно то, что в режиме изоляции "реад комиттед снапшот" читающие транзакции не "держат" пишущие транзакции и наоборот. И при этом прочитанные данные всегда согласованы, как при repeatable read.
11. Сан Саныч (herfis) 11.11.16 15:31
Ну и да - включение режима "реад комиттед снапшот" по сути меняет поведение MSSQL с "блокировочника" на "версионник". MSSQL начинает фигачить версии изменяемых строк в tempdb и при чтении брать данные подходящих версий оттуда, а не из родных таблиц.
В отличие от старых уровней изоляции - это механизм совершенно другого качества. Поэтому его еще надо явно активировать в свойствах конкретной БД.
12. Артём Андриянов (CSiER) 11.11.16 16:08
(7) headMade, спасибо за ответ, вроде все понятно, но, чувствую, только повторение шагов автора с профайлером окончательно расставят все точки на "и" в моей голове )
13. Анатолий Бритько (headMade) 11.11.16 16:17
(8) starik-2005,
помнится, говорит о том, что блокировки MS SQL все-таки устанавливает.

Конечно же блокировки всегда устанавливаются. Важное значение имеет с каким уровнем изоляции они устанавливаются и когда они снимаются.
В некоторых случаях блокировка держится до конца транзакции (при repeatable read), а в некоторых снимается сразу после выполнения запроса (read commited).

но даже наличие WITH (NOLOCK) не гарантирует их отсутствие.
- естественно можно еще рассматривать блокировки стабильности схемы или блокировки намерений, но они на параллельность работы пользователей в 1С никак не влияют.
14. Sergey Andreev (starik-2005) 11.11.16 16:34
(13) лично я вот конкретно на это отвечал:
Когда вы включаете управляемый режим блокировок, то начинает работать менеджер блокировок 1С. И соответственно сервер 1С решает что для MSQL будет достаточным уровень изоляции транзакций read commited т.к. за блокировку данных будет отвечать сам сервер 1С. В этом случае MSQL фактически ничего не блокирует.


А есть такая еще штука, как эскалация блокировок. Может внезапно оказаться, что блокируется вся таблица в SQL, при том 1С вроде как заблокировала только часть данных (до 100к).
15. Сан Саныч (herfis) 11.11.16 17:28
(14)
А есть такая еще штука, как эскалация блокировок. Может внезапно оказаться, что блокируется вся таблица в SQL, при том 1С вроде как заблокировала только часть данных (до 100к).

Ну, эти страшилки остались в прошлом, слава богу. Такое было не редкость в режиме автоматических блокировок, где на MSSQL при проведении использовался serializable - самый строгий режим изоляции, беспощадный по блокировкам. В postgresql вообще были табличные блокировки.
А сейчас на версионниках с управляемыми блокировками одинэсник в самом деле фактически самолично управляет блокировками. Я не представляю, что нужно сделать, чтобы спровоцировать экскалацию блокировок СУБД в этой ситуации. Перезаписывать значительную часть большой таблицы в одной транзакции, разве что. Так тогда так и так разница невелика. И то помешает эта эскалация только другим транзакциям на запись.
16. Виталий Попов (Сурикат) 11.11.16 23:10
(9) Sergey.Noskov,
1С ставит только SET READ_COMMITTED_SNAPSHOT ON
Хотя на MSDN написано (https://msdn.microsoft.com/ru-ru/library/tcbchxcb(v=vs.110).aspx):
Перед использованием в транзакциях изоляция моментального снимка должна быть включена путем установки параметра базы данных ALLOW_SNAPSHOT_ISOLATION в значение ON. Это приводит к активизации механизма сохранения версий строк во временной базе данных (tempdb).

Собственно вопрос был:
Как работает READ_COMMITTED_SNAPSHOT без ALLOW_SNAPSHOT_ISOLATION?
17. Анатолий Бритько (headMade) 12.11.16 10:04
(16) Сурикат,
ссылка нерабочая
18. TMV 12.11.16 10:37
(17) headMade, там последние 2 символа зацепило. Вот верная
19. Павел Алексеенко (qwinter) 12.11.16 23:36
(15) herfis,
Ну, эти страшилки остались в прошлом, слава богу.

обмен 500 новых контрагентов с 10 дополнительными значениями. Сущий пустяк правда?)
20. Яков Коган (Yashazz) 14.11.16 15:04
А, ещё один пересказ общедоступных источников...
21. Максим Кузнецов (Makushimo) 15.11.16 05:40
(20) Yashazz,
Ну не вредничайте.
Статья полезная, а общедоступные источники еще нужно найти и вычитать там именно то что нужно.
Статья хорошая
kuzyara; ilya_petrov; +2 Ответить
22. Сан Саныч (herfis) 15.11.16 10:16
(19)
обмен 500 новых контрагентов с 10 дополнительными значениями. Сущий пустяк правда?)

Скорее белиберда какая-то.