Уровень 2.0
В этом задании вы узнаете:
- Требования ACID
- Механизм многоверсионности (MVCC)
- Механизм предзаписи транзакций
- Уровни изоляции и аномалии
Требования ACID
Транзакция — это последовательность операций с базой данных, удовлетворяющая следующим требованиям:
- Atomicity (атомарность) — транзакция выполняется полностью, либо не выполняется совсем
- Consistency (согласованность) — транзакция переводит базу данных из одного корректного состояния в другое
- Isolation (изоляция) — конкурирующие транзакции не оказывают влияния друг на друга
- Durability (долговечность) — после успешного завершения транзакции ее изменения не могут быть утеряны
Атомарность требует выполнения всех операций внутри транзакции. В случае прерывания выполнения транзакции ее промежуточные результаты не должны оставлять никаких следов в базе данных.
Границы транзакции в Pangolin определяются SQL-командами BEGIN (начало) и COMMIT (фиксация). Для прерывания транзакции с отменой изменений используется команда ROLLBACK (откат). Также возможна частичная отмена изменений внутри транзакции. Для этого используются команды SAVEPOINT (установка точки сохранения внутри транзакции) и ROLLBACK TO (откат к точке сохранения — отмена изменений, совершенных после точки сохранения).
Управление транзакциями может осуществляться и без использования указанных команд. Способ управления в этом случае зависит от клиентского драйвера, реализующего клиент-серверный протокол.
Под изоляцией понимается возможность параллельного выполнения транзакций без создания помех друг другу. Нарушение изоляции может приводить к некорректному состоянию базы данных. Такие ситуации называются аномалиями конкурентного доступа.
Обеспечение полной изоляции влечет уменьшение пропускной способности при конкурентном доступе. Поэтому на практике, как правило, используется ослабленная изоляция, допускающая возникновение некоторых аномалий в данных при их конкурентной обработке несколькими транзакциями.
Атомарность и изоляция транзакций служат для обеспечения согласованности данных на уровне СУБД. Помимо этого согласованность также обеспечивается ограничениями целостности, такими как NOT NULL, UNIQUE или CHECK, а также ограничениями PRIMARY KEY и FOREIGN KEY.
Однако СУБД не может полностью гарантировать обеспечение согласованности данных на своем уровне. Во-первых, некоторые условия согласованности в силу своей сложности не могут быть определены с использованием ограничений целостности. Во-вторых, возможно появление некоторых аномалий в данных в зависимости от выбранного уровня изоляции транзакций. В конечном итоге гарантия согласованности обеспечивается приложением, а СУБД в этом ему помогает.
Под долговечностью понимается невозможность потери изменений в случае сбоя после фиксации транзакции. На уровне СУБД это означает, что при фиксации транзакции ее изменения должны быть гарантированно записаны на энергонезависимое хранилище.
В Pangolin транзакционными являются не только команды DML, такие как INSERT, UPDATE, DELETE и SELECT, но и большая часть команд DDL, таких как CREATE, ALTER и DROP.
Механизм MVCC
Свойства атомарности, согласованности и изоляции транзакций в Pangolin обеспечиваются механизмом многоверсионного управления конкурентным доступом (Multi Version Concurrency Control — MVCC) с изоляцией на основе снимков данных.
Строка в таблице базы данных является единицей ресурса, разделяемого между двумя транзакциями. Если две параллельные транзакции являются читающими, то они не нарушают изоляцию друг друга. Если обе транзакции пишущие, то одной из транзакций придется дождаться выполнения изменений другой.
Обработка ситуации, когда одна транзакция читает строку, в то время как другая пытается ее изменить, может осуществляться различными способами.
В простейшем случае транзакции могут блокировать друг друга, но тогда страдает производительность. Pangolin же использует механизм многоверсионности. Это означает, что при изменении строки создается новая ее физическая версия с сохранением старой. В этом случае каждая из транзакций работает со своей версией строки, и транзакции не блокируют друг друга.
Версии строк
Версии строк (tuples) необходимо как-то отличать. Для этого каждая из них на странице данных в заголовке строки хранит две метки:
xmin— номер транзакции, создавшей версию строкиxmax— номер транзакции, удалившей версию строки
Этими метками определяется время жизни версии строки.
Структура версий строк более подробно будет рассмотрена в лекциях «Структура данных» и «Внутристраничная очистка».
Номера транзакций (xid) в PostgreSQL выдаются последовательно и закольцованы в силу ограниченности размера счетчика 32 битами. Так или иначе, сервер всегда может определить возраст каждой из транзакций и порядок их создания.
В Pangolin номер транзакции определяется 64 битами и строится с использованием 32-битного смещения относительно обычных номеров транзакций, используемых в PostgreSQL. В силу этого в Pangolin зацикливание счетчика транзакций маловероятно.
Номер текущей транзакции можно узнать с помощью функции pg_current_xact_id().
Счетчик транзакций более подробно рассматривается в лекции «Заморозка версий строк».
Помимо меток xmin и xmax для обеспечения многоверсионности сервер должен знать статусы имеющихся транзакций. Каждая транзакция может быть активна, завершена фиксацией или обрывом.
Для хранения статусов транзакций используется специальная структура в памяти CLOG (Commit Log), в ней каждой транзакции отводится два бита, выставление которых соответствует фиксации или обрыву транзакции.
Интересно, что значения xmin и xmax можно узнать с использованием SQL-запроса, например:
SELECT *, xmin, xmax FROM t;
Это означает, что если параллельная транзакция изменит запрашиваемую строку, приведенный выше запрос отобразит номер параллельной транзакции в поле xmax. Таким образом, одна транзакция может «подсмотреть» за действиями другой и, строго говоря, нарушается свойство изоляции.
Снимки данных
Многоверсионность используется для построения снимков данных с целью изоляции параллельных транзакций.
Транзакция, запрашивая набор строк, должна видеть только одну из имеющихся версий каждой строки (или не видеть ни одной). Для этого она строит снимок, в котором видны самые последние версии уже зафиксированных данных и не видны еще не зафиксированные. Таким образом, в снимок попадают версии строк, соответствующие моменту его создания.

На рисунке строка 1 не попала в снимок, так как была удалена до момента создания снимка. Актуальная версия строки 2 попала в снимок, так как после последней фиксации транзакции во время использования снимка новых версий строки 2 не создавалось. Также в снимок попала строка 3, но она не является актуальной, так как во время работы со снимком параллельная транзакция ее удалила (и зафиксировала изменение).
Таким образом строится согласованная картина данных на момент создания снимка. При этом снимок — это не физическая копия данных, а всего несколько чисел.
Снимки данных более подробно будут рассмотрены в лекции «Снимки данных».
Очистка устаревших версий строк
Механизм MVCC является эффективным с точки зрения производительности при параллельном выполнении транзакций.
Однако недостатком механизма MVCC является накопление устаревших версий строк, которые необходимо каким-то образом вычищать из базы данных.
Для каждой базы данных существует такой номер транзакции xid, что все старые версии строк, удаленные транзакциями с меньшими номерами, уже не видны ни в одном снимке данных.
Этот номер называется горизонтом очистки или горизонтом базы данных, а устаревшие версии строк — «мертвыми».
Очистка «мертвых» строк осуществляется автоматически группой процессов под общим названием autovacuum. Также очистка может осуществляться вручную такими командами, как VACUUM и VACUUM FULL.
Очистка «мертвых» версий строк подробнее рассматривается в лекции «Очистка».
Журнал предзаписи WAL
Свойство долговечности транзакций в Pangolin обеспечивается использованием журнала предзаписи (Write-Ahead Log — WAL).
В процессе работы Pangolin для повышения производительности часть данных находится в оперативной памяти, в том числе в кеше буферов. Запись указанных данных на энергонезависимое хранилище осуществляется в отложенном режиме с некоторой периодичностью. Следовательно, в случае сбоя часть данных может потеряться, а оставшаяся часть оказаться в рассогласованном виде.
Для предотвращения таких ситуаций и реализации требования долговечности транзакции в Pangolin используется журналирование. Каждое действие в оперативной памяти журналируется, то есть в энергонезависимом хранилище сохраняется запись, содержащая информацию, достаточную для воспроизведения того же действия при восстановлении.
Журнальная запись попадает в энергонезависимое хранилище всегда раньше соответствующей измененной страницы. Поэтому данный журнал называется журналом предзаписи (Write-Ahead Log — WAL).
Журналирование практически не влияет на общую производительность, так как запись ведется последовательно и, как правило, журнальные записи меньше по объему исходных страниц.
Журнал WAL используется не только для восстановления данных после сбоя. Он также используется в процессах репликации и резервного копирования.
Журнал WAL подробнее рассматривается в лекции «Журнал предзаписи».
Уровни изоляции и аномалии
Требование изоляции транзакций представляет наибольшую сложность для реализации в СУБД. Как было сказано выше, на практике, как правило, используется ослабленная изоляция, допускающая возникновение некоторых аномалий в данных при их конкурентной обработке несколькими транзакциями.
Аномалии конкурентного доступа
Существует множество известных аномалий конкурентного доступа и какое-то количество пока неизвестных. Тем не менее исторически так сложилось, что в стандарт SQL попала только часть известных аномалий, на основе которых были определены возможные уровни изоляции транзакций.
Итак, стандарт SQL описывает следующие аномалии конкурентного доступа:
Потерянные изменения
Одна транзакция может перезаписать изменения, зафиксированные другой транзакцией, без учета выполненных изменений.
Например, две транзакции вносят изменения в состояние одного и того же счета. Первая транзакция считывает сумму счета 5000 руб., затем вторая транзакция считывает ту же сумму. Первая транзакция увеличивает сумму счета на 1000 руб. и фиксирует это изменение. Вторая транзакция, не учитывая изменений первой, увеличивает сумму счета на 500 руб. и фиксирует это изменение. В результате на счету оказывается 5500 руб., хотя должно быть 6500 руб. Клиент потерял 1000 руб.
Грязное чтение
Одна транзакция читает еще незафиксированные изменения другой транзакции.
Например, одна транзакция увеличивает сумму счета №1 на 1000 руб., не фиксируя изменения. Вторая осуществляет перевод всех средств с данного счета на счет №2 с учетом добавленной 1000 руб. Выполнение первой транзакции обрывается с откатом выполненных изменений. В результате на счете №2 оказалась лишняя 1000 руб.
Неповторяющееся чтение
Одна транзакция при повторном чтении одной и той же строки получает другой результат, так как между чтениями другая транзакция изменила или удалила данную строку с фиксацией результатов.
Например, правилом согласованности запрещается перевод средств со счетов, на которых сумма менее 1000 руб. Первая транзакция считывает сумму счета 1500 руб. Вторая транзакция уменьшает сумму того же счета на 1000 руб. и фиксирует изменения. Первая транзакция осуществляет перевод с указанного счета 500 руб. на другой счет, полагая, что на счету 1500 руб. В результате нарушено правило согласованности. Если бы первая транзакция повторно считала сумму счета, то в результате было бы получено 500 руб.
Фантомное чтение
Одна транзакция при повторном чтении набора строк получает другой результат, так как между чтениями другая транзакция добавила строки, удовлетворяющие тому же условию, с фиксацией результата.
Например, правилом согласованности запрещается одному клиенту иметь более 10 счетов. Первая транзакция получает количество счетов у клиента, равное 9, и принимает решение, что можно добавить новый счет. Вторая транзакция делает то же самое и добавляет новый счет с фиксацией результатов. Первая транзакция добавляет новый счет. В результате нарушено правило согласованности. У клиента оказалось 11 счетов.
Уровни изоляции в стандарте SQL
В зависимости от допустимых аномалий конкурентного доступа в стандарте SQL определены следующие уровни изоляции:
| Потерянные изменения | Грязное чтение | Неповторяющееся чтение | Фантомное чтение | Другие аномалии | |
|---|---|---|---|---|---|
| Read Uncommitted | Да | Да | Да | Да | |
| Read Сommitted | Да | Да | Да | ||
| Repeatable Read | Да | Да | |||
| Serializable |
В соответствии со стандартом SQL аномалия потерянного изменения недопустима ни на одном уровне изоляции. Уровень изоляции Read Committed помимо этого не допускает грязного чтения. Уровень Repeatable Read дополнительно не допускает аномалию неповторяющегося чтения. Уровень Serializable не допускает никаких известных или неизвестных аномалий конкурентного доступа.
Уровни изоляции в Pangolin
Уровни изоляции в существующих СУБД несколько отличаются от соответствующих уровней, предусмотренных стандартом SQL. Это связано с возможностями конкретной реализации.
В Pangolin изоляция реализована на основе снимков данных в сочетании с механизмом многоверсионности (MVCC). В целом уровни изоляции реализованы строже, чем требует того стандарт:
| Потерянные изменения | Грязное чтение | Неповторяющееся чтение | Фантомное чтение | Другие аномалии | |
|---|---|---|---|---|---|
| Read Uncommitted | Не реализован | Не реализован | Не реализован | Не реализован | Не реализован |
| Read Сommitted | Частично | Да | Да | Да | |
| Repeatable Read | Да | ||||
| Serializable |
Уровень изоляции Read Uncommitted отдельной реализации не имеет. Он может быть указан, но фактически работать будет как Read Committed, то есть не будет допускать аномалии грязного чтения.
Реализация уровня изоляции Read Committed в целом соответствует стандарту, однако в некоторых случаях не предотвращает аномалию потерянных изменений. Пример такого случая будет разобран в лабораторной работе.
На уровне изоляции Read Committed транзакция никогда не прерывается в случае возникновения аномалий.
Реализация уровня изоляции Repeatable Read в отличие от стандарта также не допускает аномалию фантомного чтения.
Реализация уровня изоляции Serializable соответствует стандарту и основана на механизме предикатных блокировок, который будет рассмотрен в теме «Блокировки».
Выбор уровня изоляции в Pangolin
Указание уровня изоляции транзакции в Pangolin может осуществляться двумя способами.
-
В команде начала транзакции, например:
BEGIN ISOLATION LEVEL REPEATABLE READ; -
Сразу после начала транзакции, например:
BEGIN;SET TRANSACTION ISOLATION LEVEL READ SERIALIZABLE;Узнать используемый уровень изоляции текущей транзакции можно, посмотрев значение параметра:
SHOW transaction_isolation;
Уровень изоляции Read Committed применяется по умолчанию. Однако это поведение можно изменить с использованием параметра default_transaction_isolation.
Уровень изоляции Read Committed удобен тем, что транзакция не прерывается в случае возникновения ошибок согласованности при параллельной работе других транзакций. Соответственно не требуется повторного ее выполнения. Ценой этого является необходимость предотвращения различных аномалий (за исключением грязного чтения) разработчиком приложения.
В частности, для предотвращения аномалии неповторяющегося чтения могут использоваться атомарные операторы SQL, такие как общие табличные выражения (Common Table Expressions — СТЕ) — запросы WITH или оператор INSERT FOR UPDATE.
В отдельных случаях может потребоваться наложить явные блокировки, например с помощью оператора SELECT FOR UPDATE, что сводит на нет преимущества многоверсионности.
Важным случаем, при котором могут быть получены несогласованные данные на уровне изоляции Read Committed, является случай использования функций с категорией изменчивости VOLATILE внутри оператора SQL.
Дело в том, что если в такой функции выполняется запрос, то он видит данные, несогласованные с данными основного запроса.
Решением данной проблемы является использование категории изменчивости STABLE в функциях, выполняющих запросы и не имеющих побочных эффектов.
Уровень изоляции Repeatable Read не допускает часть аномалий, возникновение которых возможно на уровне Read Committed. Однако ценой этого является возникновение ошибок сериализации с прерыванием конфликтующей транзакции. Ответственность за обработку таких ошибок с повторным выполнением транзакций лежит на разработчике приложения.
В то же время, если транзакция является только читающей, то на уровне Repeatable Read возникновение ошибок сериализации исключено. Поэтому данный уровень хорошо подходит для построения отчетов, где требуется выполнение нескольких запросов в одной транзакции.
Уровень изоляции Serializable полностью освобождает разработчика приложения от учета различных аномалий. Однако, как и на уровне Repeatable Read, требует от него отслеживания и обработки ошибок сериализации. Параллельные транзакции на данном уровне выполняются так, как если бы они выполнялись последовательно в некотором порядке. Реализовано это с использованием механизма предикатных блокировок. Ценой этого являются большие накладные расходы и значительное количество прерванных транзакций, что может существенно сказываться на производительности.
Для применения уровня изоляции Serializable параллельные транзакции также должны быть с таким же уровнем изоляции. В противном случае будет применяться уровень изоляции Repeatable Read, несмотря на явное указание уровня Serializable.
Также в Pangolin имеется ограничение применения уровня изоляции Serializable на физических репликах.
Итоги
- Транзакции удовлетворяют требованиям атомарности, согласованности, изоляции и долговечности
- Атомарность, согласованность и изоляция транзакций в Pangolin обеспечиваются механизмом многоверсионности (MVCC) с построением снимков данных
- Долговечность транзакций в Pangolin обеспечивается журналом предзаписи (WAL)
- Обычно используется ослабленная изоляция транзакций, допускающая некоторые аномалии в данных
- В Pangolin предусмотрено три уровня изоляции транзакций: Read Committed, Repeatable Read и Serializable
Самопроверка
Вопрос 1
Для обеспечения какого свойства транзакции могут использоваться ограничения целостности?
Вопрос 2
Какие аномалии конкурентного доступа могут возникать в Pangolin на уровне изоляции Read Committed? Выберите все верные варианты ответа.
Вопрос 3
Какие уровни изоляции в Pangolin предотвращают аномалию неповторяющегося чтения? Выберите все верные варианты ответа.
Вопрос 4
Какие средства в Pangolin обеспечивают свойство долговечности транзакций?