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

В сети и в книгах довольно много информации с описанием уровней изоляции транзакций, их особенностей и отличий. Когда читаешь – всё вроде понятно, но при столкновении с практическими задачами возникают трудности.

Чтобы “пощупать”, как ведёт себя система с разными настройками, я сделал элементарный пример с одной единственной таблицей – результаты экспериментов описаны ниже. Дополнительно выяснилось, что система ведёт себя по-разному не только с разными настройками, но и с одинаковыми настройками под разными СУБД (Postgre и MS SQL).

Итак, для тестирования мы создадим пустую конфигурацию 1С на платформе 8.3 (я использовал 8.3.8.1747), в режиме толстого клиента, с одним единственным справочником ДолжностиОрганизаций. В этот справочник добавим единственный элемент с кодом “ТестЭл” и наименованием “!Тестовый элемент”.

Для тестирования мы будем из двух параллельных сеансов читать, а затем попытаемся записать эту единственную запись, и будем наблюдать блокировки, возникающие при конкурентном обращении к одному и тому же ресурсу.

Делать это будем с помощь внешней обработки с простым кодом:

НачатьТранзакцию();

    ТестЭлСсылка = Справочники.ДолжностиОрганизаций.НайтиПоКоду("ТестЭл");
    Предупреждение("Прочитан: "+ТестЭлСсылка.Наименование);        

    ТестЭлОбъект = ТестЭлСсылка.ПолучитьОбъект();
    ТестЭлОбъект.Наименование = "!Тестовый элемент "+ТекущаяДата();
    ТестЭлОбъект.Записать();
    Если Вопрос("Записать? "+ТестЭлОбъект.Наименование,РежимДиалогаВопрос.ДаНет) 
    = КодВозвратаДиалога.Да Тогда
        ЗафиксироватьТранзакцию();
    Иначе
        ОтменитьТранзакцию();
    КонецЕсли;

Для начала конфигурация под MS SQL у нас находится в режиме автоматических блокировок, что соответствует уровню изоляции repeatable read (повторяемое чтение) для Справочников:

repeatable-read-vs-read-commited-1

Запускаем параллельно два сеанса, в обоих сеансах запускаем код обработки и проходим этап чтения справочника:

repeatable-read-vs-read-commited-2

При нажатии на ОК в любом из окон далее происходит попытка записать этот элемент внутри транзакции. Поскольку каждым из сеансов на этот элемент установлена блокировка на чтение (котороя сохраняется до конца транзакции) – при попытке записи этого элемента под MS SQL получаем ошибку таймаута:

repeatable-read-vs-read-commited-3

После ошибки таймаута транзакция во 2 сеансе откатывается, в 1 сеансе мы можем выполнить запись. На скриншоте запись элемента справочника уже выполнена, но сама транзакция ещё не завершилась:

repeatable-read-vs-read-commited-4-0

При этом во 2 сеансе мы можем открыть форму списка справочника и увидеть там изменённое наименование элемента справочника, которое изменено не завершённой ещё транзакцией.

repeatable-read-vs-read-commited-4-1

Это и есть иллюстрация “грязного чтения” (dirty read). Отображение формы списка справочника в данном случае осуществляется без учёта завершённости транзакций. При этом если в 1 сеансе мы нажмём НЕТ (отменим транзакцию) – только после обновления списка во 2 сеансе Наименование нашего тестового элемента вернётся в нетронутое состояние:

repeatable-read-vs-read-commited-5

Интересно отметить, что с указанными настройками 1С база под PostgreSQL ведёт себя совершенно по-другому. После нажатия ОК после чтения PostgreSQL никакой таймаут не возвращает, а начинает спокойно ждать, когда освободится элемент от блокировки на чтение, чтоб его записать. Первый раз когда я с этим столкнулся – подождал минут 15 и хотел уже снимать повисший сеанс. Но после того как мы нажмём ОК во втором сеансе – возникает ошибка на взаимоблокировке (deadlock):

repeatable-read-vs-read-commited-6

Это происходит потому, что наши 1 и 2 сеанс начинают ждать друг друга – каждый на попытке записи ждёт другой сеанс, пока тот снимет блокировку на чтение. После того как второй сеанс был выбран жертвой взаимоблокировки – его транзакция отменяется, и в первом сеансе выполняется код записи элемента внутри транзакции, о чём нам свидетельствует следующее диалоговое окно:

repeatable-read-vs-read-commited-4

“Грязного чтения” при отображении списка Справочника в случае с PostgreSQL не происходит. Пока транзакция не завершилась – в другом сеансе мы видим в списке не изменённое название элемента справочника:

repeatable-read-vs-read-commited-5

На этом рассмотрение режима repeatble read (повторяемое чтение, “автоматические блокировки” в 1С) мы закончим. Мораль repeatble read – если мы что-то прочитали в транзакции – никто из других транзакций не сможет изменить эти записи, пока наша транзакция не завершится (даже если мы ничего в ней записывать и не будем).

Теперь перейдём к рассмотрению режима read commited в MS SQL.

Для этого переведём конфигурацию в режим управляемых блокировок. Для этого переведём режим управления блокировками в состояние Управляемый и останемся в режиме совместимости с 8.2:

repeatable-read-vs-read-commited-11

После применения изменений запустим в двух сеансах ту же обработку. Теперь при чтении элемента блокировка снимается после прочтения – и поэтому в одном из сеансов внутри транзакции мы можем осуществить запись элемента:

repeatable-read-vs-read-commited-7

При попытке записать тот же элемент во втором сеансе получим ошибку-таймаут:

repeatable-read-vs-read-commited-8

Закрыв окно с таймаутом – можем наблюдать грязное чтение изменённого элемента в незавершённой транзакции:

repeatable-read-vs-read-commited-9

Это происходит потому что при выводе формы справочника данные читаются 1Сом вне транзакции, без учёта установленных блокировок. Если же мы попытаемся во 2 сеансе снова запустить обработку с начала – на этот раз получим таймаут уже на операции чтения:

repeatable-read-vs-read-commited-10

Поскольку при записи элемента на него была установлена эксклюзивная блокировка, а транзакция в 1 сеансе ещё не завершилась.

Собственно, мораль read commited, в нашем случае: чтение данных в транзакции НЕ блокирует их автоматически от записи. Для принудительной блокировки нужно использовать объект БлокировкаДанных.

Ну и теперь переведём базу SQL в режим read commited snaphot, и попробуем понять, есть ли отличия работы в этом режиме? Режим совместимости базы установим в “Не использовать”:

repeatable-read-vs-read-commited-11

При попытке параллельной записи элемента справочника во 2 сеансе мы также получаем таймаут, только его возвращает теперь не СУБД MS SQL, а сама платформа 1С:

repeatable-read-vs-read-commited-12

Но теперь у нас исчезла проблема “грязного чтения” формы списка справочника:

repeatable-read-vs-read-commited-13

И также мы можем повторно читать Наименование справочника во 2 сеансе в транзакции – читается Наименование, которое было до изменения, транзакция которая пишет это Наименование в 1 сеансе нам не мешает его читать:

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

Автор статьи – Илья Евгеньевич Петров

г. Самара, 2016 г.

Комментарии / обсуждение (7):

  1. Shetan

    На какой момент фиксируется “снимок” snapshot-а? Например начали транзакцию 1, выполняем какие то действия, но данные еще не читали и не записывали. В это время транзакция 2 изменила наши данные и завершилась. Транзакция 1 читает данные. Какие это будут данные? Те которые были до начала транзакции 1 или те которые получились в результате работы транзакции 2?

  2. Александр

    Добрый день! Первый и второй скриншоты с настройками совместимости неправильные.
    На первом должно быть:
    – Режим управления блокировкой данных – Автоматический;
    – Режим совместимости – 8.2.13.
    На втором должно быть:
    – Режим управления блокировкой данных – Управляемый;
    – Режим совместимости – 8.2.13.
    На третьем все правильно:
    – Режим управления блокировкой данных – Управляемый;
    – Режим совместимости – Не использовать.
    За статью спасибо!

  3. Александр Сокол

    Спасибо, очень познавательный материал!
    Одно замечания, второй скрин с настройками совместимости явно не верный (он такой-же как и третий), я так понимаю, там должно быть “Управляемый” и “Версия 8.2.13”.

  4. marivgo

    Добрый день! Каким образом можно гарантированно избежать грязного чтения при формировании, например, отчета в 1С без использования read commited snaphot на SQL-сервере? Получается, что читать данные запросом, чтобы они всегда были “чистыми”, можно только в рамках транзакции, даже если в рамках этой транзакции мы сами ничего в базу не записываем?

    • Татьяна Гужавина

      Доброго дня, Мария!
      Никак. Если транзакицю не начинать – будет грязное чтение (например, в консоли запросов). А если начинать транзакцию – будет таймаут, пока чужие блокировки не снимутся.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Вход на сайт

Зарегистрироваться

Подтверждение регистрации будет отправлено на указанный e-mail.

Я подтверждаю, что ознакомлен(а) с Пользовательским соглашением, принимаю его условия и даю свое согласие на обработку моих персональных данных.

Восстановить доступ

E-mail или логин

Ссылка на создание нового пароля будет отправлена на указанный e-mail.