Архитектура
Изоляция
12
Авторские права
© Postgres Professional, 2020 год.
Авторы: Егор Рогов, Павел Лузанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Транзакции и их свойства
Уровни изоляции в стандарте SQL
Особенности реализации PostgreSQL
3
Транзакции и их свойства
Транзакция — множество операций,
которые переводят базу данных из одного
корректного состояния в другое корректное
при условии, что транзакция выполнена полностью
и без помех со стороны других транзакций
атомарность: транзакция либо выполняется полностью,
либо не оставляет никаких следов
согласованность определяется ограничениями целостности
и семантикой приложения
неполная изоляция приводит к нарушению корректности (аномалиям)
при конкурентном выполнении транзакций
Согласованность
Атомарность
Изоляция
Транзакцией называется множество операций, выполняемое
приложением, которое переводит базу данных из одного корректного
состояния в другое корректное состояние (согласованность)
при условии, что транзакция выполнена полностью (атомарность)
и без помех со стороны других транзакций (изоляция).
Корректное (согласованное) состояние определяется как
ограничениями целостности, заданными на уровне базы данных, так и
семантикой приложения.
Идеальная изоляция транзакций гарантирует, что на работу
приложения не окажут влияния никакие другие конкурентно
выполняющиеся транзакции. Однако если ослабить требования
к изоляции, то транзакция, которая корректно выполнялась в базе
данных в одиночестве, может выдавать некорректные результаты при
наличии других транзакций — из-за того, что при выполнении
операторы разных транзакций могут чередоваться. Такие некорректные
ситуации называются аномалиями.
Реализация полной изоляции — сложная задача, сопряженная
с уменьшением пропускной способности системы. На практике как
правило применяется именно ослабленная изоляция, и поэтому важно
понимать, какие это влечет последствия.
Четвертое свойство ACID — долговечностьмы рассмотрим позже,
в теме «Обзор журналирования».
4
Read Uncommitted
Должен предотвращать аномалии
потерянные изменения:
транзакция может перезаписать изменения других транзакций,
зафиксированных после ее начала
Не реализован в PostgreSQL
работает как Read Committed
Стандарт SQL допускает четыре уровня изоляции, которые
определяются в терминах аномалий, которые допускаются при
конкурентном выполнении транзакций на этом уровне.
Самый слабый уровень — Read Uncommitted, который (как следует из
названия) позволяет увидеть даже незафиксированные данные.
Согласно стандарту на этом уровне должна предотвращаться
единственная аномалия:
Потерянные изменения (lost updates). Если после начала
транзакции T1 другая транзакция T2 изменила и зафиксировала
строки, транзакция T1 может перезаписать эти изменения.
Этот уровень вводился как требующий наименьших накладных
расходов, но PostgreSQL может без потери эффективности работать на
более строгом уровне. Поэтому в PostgreSQL такой уровень не
реализован и говорить о нем мы больше не будем.
5
Read Committed
Должен предотвращать аномалии
потерянные изменения
грязное чтение:
запрос видит незафиксированные изменения других транзакций
Реализация в PostgreSQL
используется по умолчанию
Каждый следующий уровень изоляции строже, чем предыдущий.
Поэтому уровень Read Committed должен предотвращать не только
потерянные изменения, но и еще одну аномалию:
«Грязное» чтение (dirty read). Транзакция T1 может читать строки,
измененные, но еще не зафиксированные, транзакцией T2. Отмена
изменений (ROLLBACK) в T2 приведет к тому, что T1 прочитает
данные, которых никогда не существовало.
Существует много других аномалий, которые допускаются на уровне
Read Committed. Разработчик должен всегда помнить о возможных
проблемах и при необходимости вручную использовать блокировки.
В PostgreSQL (как и во многих других СУБД) именно этот уровень
изоляции используется по умолчанию — как компромисс между
строгостью изоляции и эффективностью.
Заметим, что в PostgreSQL на уровне Read Committed можно потерять
изменения — такой пример будет показан в демонстрации.
7
Repeatable Read
Должен предотвращать аномалии
потерянные изменения
грязное чтение
неповторяющееся чтение:
повторное чтение строки вернет другое значение,
если оно было изменено и зафиксировано другой транзакцией
Реализация в PostgreSQL
также не допускает фантомное чтение:
повторный запрос по одному и тому же условию вернет иную выборку,
если другая транзакция добавила и зафиксировала новые строки,
удовлетворяющие этому условию
для предотвращения аномалий транзакции могут обрываться
Согласно стандарту, в дополнение к потерянным изменениям и
грязным чтениям уровень изоляции Repeatable Read должен
предотвращать аномалию, которую мы видели, когда рассматривали
уровень Read Committed:
Неповторяющееся чтение (non-repeatable read). После того, как
транзакция T1 прочитала строку, транзакция T2 изменила или
удалила эту строку и зафиксировала изменения (COMMIT). При
повторном чтении этой же строки транзакция T1 видит, что строка
изменена или удалена.
Реализация PostgreSQL более строгая, чем того требует стандарт,
и предотвращает аномалию
Фантомное чтение (phantom read). Транзакция T1 прочитала набор
строк по некоторому условию. Затем транзакция T2 добавила
строки, также удовлетворяющие этому условию. Если транзакция T1
повторит запрос, она получит другую выборку строк.
На самом деле существуют и другие аномалии (не отраженные
в стандарте), которые допускаются в PostgreSQL на уровне Repeatable
Read.
От уровня Read Committed этот уровень изоляции отличается и тем,
что на нем транзакция может быть оборвана, чтобы не допустить
аномалию (такую транзакцию надо повторять). На уровне Read
Committed этого не происходит никогда — если стоит выбор между
корректностью и эффективностью, предпочтение всегда отдается
эффективности.
9
Serializable
Должен предотвращать любые аномалии
результат конкурентного выполнения совпадает с результатом
выполнения транзакций в какой-либо последовательности
Реализация в PostgreSQL
для предотвращения аномалий транзакции могут обрываться
для правильной работы уровень Serializable должен использоваться
всеми транзакциями
уровень не работает на репликах
Полное отсутствие каких бы то ни было аномалий достигается, если
команды, выполняемые в конкурентно работающих транзакциях,
приводят к такому же результату, какой получился бы в случае
последовательного — одна завершилась, следующая началась —
выполнения этих транзакций (при каком-нибудь порядке выполнения).
В PostgreSQL этот уровень реализован и работает, если все транзакции
в системе используют уровень Serializable.
Следует учитывать, что реализация имеет определенные ограничения:
до версии 12 запросы не распараллеливались;
запросы не могут выполняться на репликах;
транзакции могут обрываться чаще, чем это действительно
необходимо (таким образом, страдает эффективность, поскольку
оборванные транзакции приходится выполнять повторно).
11
Итоги: стандарт SQL
потерянные
обновления
грязное
чтение
неповторя-
ющееся
чтение
фантомное
чтение
другие
аномалии
Read Uncommitted да да да да
Read Committed да да да
Repeatable Read да да
Serializable
Подытожим информацию.
В стандарте SQL определены четыре уровня изоляции транзакций:
Read Uncommitted, Read Committed, Repeatable Read, Serializable
(по умолчанию).
Таблица показывает соответствие между уровнями изоляции и теми
аномалиями, которые они могут допускать.
Отметим, что для обеспечения уровня изоляции Serializable
недостаточно исключить три аномалии, определенные в стандарте.
12
Итоги: PostgreSQL
* предотвращается не всегда
потерянные
обновления
грязное
чтение
неповторя-
ющееся
чтение
фантомное
чтение
другие
аномалии
Read Uncommitted
Read Committed * да да да
Repeatable Read да
Serializable
Реальные СУБД, и PostgreSQL в том числе, не реализуют требования
стандарта буквально (которые были сформулированы в те времена,
когда вопросы изоляции еще не были изучены теоретиками). Названия
уровней изоляции сохраняются, но реализация их строже, чем это
требует стандарт.
В PostgreSQL используется изоляция на основе снимков данных
(Snapshot Isolation) в сочетании с многоверсионностью. Это
определяет следующие свойства:
Грязное чтение не допускается вообще, причем без ущерба для
производительности.
Уровень Read Committed используется по умолчанию и применяется
в большинстве систем. Зачастую он требует ручной установки
блокировок, чтобы обеспечить корректность.
Уровень Repeatable Read не допускает не только неповторяемое
чтение, но и фантомное чтение (хотя и не обеспечивает полную
изоляцию). Этот уровень удобен для отчетов, поскольку позволяет
нескольким запросам видеть согласованные данные, а только
читающие транзакции никогда не обрываются.
Уровень Serializable также полностью реализован, но имеет
определенные ограничения.
13
Практика
1. Проверьте, что на уровне изоляции Read Committed
не предотвращается аномалия фантомного чтения.
2. В транзакции с уровнем изоляции Read Committed
несколько раз вызывается функция, определение которой
изменяется в это время в другой транзакции.
Что произойдет? Проверьте и объясните результат.
3. Создайте пустую таблицу.
Откройте транзакцию с уровнем изоляции Repeatable Read
(и пока не выполняйте в ней никаких команд).
В другом сеансе добавьте в таблицу строку.
Видна ли добавленная строка в открытой транзакции?
1. Действуйте так же, как было показано в демонстрации.
2. Для простоты функция может не иметь параметров и возвращать
константу. Функцию можно создать (и изменять) на основе такого
шаблона:
CREATE OR REPLACE FUNCTION f() RETURNS integer AS $$
SELECT 1;
$$ LANGUAGE sql;