Перейти к основному содержимому

Лекция 07. Транзакции

В этой главе:

  • Транзакции
  • Пример транзакции
  • Изоляция
  • Долговечность
  • Многоверсионность
  • Блокировки
  • Состояние
  • транзакций

Транзакции

Поддержка транзакций - важнейшее свойство СУБД промышленного уровня.

Транзакция — логически неделимый набор операций (ACID):

  • Atomicity (атомарность) - транзакция выполняется полностью, либо полностью отменяется;
  • Consistency (согласованность) - переводят базу данных из одного корректного состояния в другое корректное состояние;
  • Isolation (изоляция) - транзакция не должна подвергаться влиянию и сама влиять на другие транзакции, параллельно работающие в системе;
  • Durability (долговечность) - данные не должны теряться даже в случае сбоя системы.

Управление транзакциями обычно на стороне клиента. Поддержка транзакций - ответственность сервера.

Транзакция собирает несколько команд, выполняющую последовательность действий, в неделимую операцию, которая либо завершается как единое целое при ее фиксации, либо ни одно из действий не оставляет следов при ее отмене. Транзакции - средство гарантировать целостность (согласованность) данных в базе. Последовательность действий, выполняемая транзакцией не должна быть видна для других транзакций, как минимум до ее фиксации. Если при выполнении транзакции происходит ошибка, то никаких следов этих действий остаться не должно.

К транзакциям предъявляются требования ACID, указанные на слайде. В PostgreSQL транзакция представляет собой последовательность команд SQL, заключенная в блок между командами BEGIN и COMMIT. Если во время выполнения транзакции решено отказаться от фиксации, вместо COMMIT используют ROLLBACK для отката. При откате гарантируется возврат к исходному состоянию данных так, как будто транзакция и не начиналась.

Управление транзакциями чаще всего отнесено в приложение, но в PostgreSQL на стороне сервера также можно управлять транзакциями в процедурах.

https://www.postgresql.org/docs/15/tutorial-transactions.html.

https://www.postgresql.org/docs/15/xproc.html.

Пример транзакции

student@arch_db=> BEGIN; -- Начало транзакции
BEGIN
student@arch_db=*> CREATE TABLE table1 AS SELECT now() mtime; SELECT 1
student@arch_db=*> SELECT * FROM table1; -- В таблице есть данные
mtime
-------------------------------
2024-09-06 08:29:11.251527+03
(1 строка)
student@arch_db=*> ROLLBACK; -- Транзакция отменена
ROLLBACK
student@arch_db=> SELECT * FROM table1; -- Таблицы нет
ERROR: relation "table1" does not exist
СТРОКА 1: SELECT * FROM table1;
^

В примере на слайде запущена транзакция, в которой создается таблица. В PostgreSQL команда CREATE TABLE транзакционна в отличие от некоторых других СУБД. Таблица создана с использованием CTAS (Create table as select) и в ней есть одна строка. После завершении транзакции откатом видно, что таблица отсутствует. [https://www.postgresql.org/docs/15/sql-createtableas.html https://www.postgresql.org/docs/15/sql-begin.html https://www.postgresql.org/docs/15/sql-rollback.html](https://www.postgresql.org/docs/15/sql-createtableas.html https://www.postgresql.org/docs/15/sql-begin.html https://www.postgresql.org/docs/15/sql-rollback.html).

Изоляция

Как работает изоляция

Команды и транзакции работают с индивидуальными подмножествами версий строк, называемых снимками.

Механизм MVCC использует разные версии строк, команде или транзакции видимы версии строк из соответствующего снимка.

Используя снимки в PostgreSQL добиваются независимой параллельной работы множества транзакций - изоляции.

Правила изоляции:

  • одну и ту же версию строки могут читать множество транзакций, не блокируя друг- друга;
  • изменять версию строки может единственная транзакция, блокируя другие транзакции, пытающиеся изменить эту строку;
  • при блокировке строки для ее изменения читающие транзакции не блокируются.

PostgreSQL - многопользовательская и многозадачная система, в которой множество разных транзакций работают параллельно, причем необходимо обеспечить изоляцию транзакции от влияния других транзакций. В PostgreSQL используется механизм многоверсионности (Multi-version concurrency control MVCC), который позволяет разным транзакциям одновременно читать и записывать строки без приостановки работы друг-друга. Точнее, читать одну и ту же строку могут одновременно множество транзакций, даже тогда, когда эту строку изменяет какая-либо транзакция. Изменять строку в моменте может лишь одна транзакция, другие транзакции, претендующие на изменение этой же строки, должны ожидать освобождения блокировки на изменяемую строку.

При вставке INSERT создает строку, а точнее - ее первую версию. При удалении строки DELETE специально помечает первую версию строки так, чтобы ее не было видно. Но эта версии строки физически не удаляется, так как она может быть нужна другим транзакциям. Изменение строки командой UPDATE помечает первую версию строки так же, как и DELETE - и это версию далее не видно, но UPDATE тут же создает новую версию строки - и она видна. Если удаленные версии строк не нужны ни для одной транзакции, то их следует очистить. Подробнее об этом в главе "Обслуживание СУБД".

Транзакции работают с так называемыми "снимками данных", определяемыми правилами видимости версий строк. https://www.postgresql.org/docs/15/mvcc-intro.html.

Примеры изоляции

Пример 1

=> BEGIN; -- Первый сеанс
=*> CREATE TABLE bill AS SELECT g.i FROM
generate_series(1,1000000) g(i);
=*> SELECT count(*) FROM bill; -- Транзакция еще не зафиксирована
count
---------
1000000
(1 row)
# Второй сеанс в ОС. В силу изоляции таблицы нет.
$ psql -qd arch_db -c 'SELECT count(*) FROM bill'
ERROR: relation "bill" does not exist
LINE 1: SELECT count(*) FROM bill

В PostgreSQL реализованы несколько уровней изоляции транзакций в соответствие со стандартом SQL. В любом случае, изменения данных, происходящие в еще не зафиксированной транзакции не видны другим транзакциям.

В примере на слайде запущена транзакция и с помощью CTAS в таблицу записан миллион строк, что подтверждается второй командой в транзакции. Следует отметить, что изменения, вносимые в данные транзакцией обязательно видны в ней самой. Но не в других транзакциях. На слайде показано, что открыт второй сеанс, в котором выполнен тот же запрос по подсчету количества строк в таблице, что и запрос в транзакции. Но в транзакции этот запрос вернул количество строк, а в другой транзакции (в другом сеансе) сама таблица еще и не видна, так как она создавалась в транзакции, которая еще не зафиксирована.

Стандарт SQL описывает ситуацию, когда изменения в данных, производимые еще не зафиксированной транзакцией, становятся видны другим транзакциям. Эта ситуация называется "аномалия грязного чтения". В PostgreSQL такой аномалии не наблюдается ни на каком уровне изоляции транзакций.

Пример 2

=> BEGIN; -- Первый сеанс
=*> CREATE TABLE bill AS SELECT g.i FROM generate_series(1,1000000)
g(i);
=*> SELECT count(*) FROM bill; -- Транзакция еще не зафиксирована
count
---------
1000000
(1 row)
=*> COMMIT; -- Транзакция зафиксирована, результаты сохранены
$ psql -qd arch_db -c 'SELECT count(*) FROM bill' 
count
---------
1000000
(1 row)

Только тогда, когда транзакция зафиксирована COMMIT, как минимум запущенные после этого транзакции будут видеть результаты изменений, внесенные этой зафиксированной транзакцией.

На самом деле эти изменения будут видны и тем транзакциям, которые начались до фиксации и работают на уровне изоляции READ COMMITTED. Подробнее об этом далее в этой главе.

В примере на слайде в транзакции была CTAS создана таблица, а сама транзакция была зафиксирована. В другом сеансе, запущенном после фиксации первой транзакции был выполнен запрос, подсчитывающий количество строк в таблице. И этот запрос выдал правильный результат.

https://www.postgresql.org/docs/15/sql-commit.html.

Долговечность

$ sudo killall -QUIT postgres # Имитация отказа 
$ ps f -C postgres
PID TTY STAT TIME COMMAND
$ sudo systemctl start postgresql
$ psql -qd arch_db -c 'SELECT count(*) FROM bill'
count
---------
1000000
(1 row)

Здесь:

  • -QUIT - флаг завершения работы без контрольной точки;
  • значение count=1000000 показывает, что после сбоя данные на месте.

Фиксация данных в PostgreSQL гарантирует сохранность данных (Durability - долговечность). В примере на предыдущем слайде была зафиксирована транзакция, в результате которой была создана таблица с миллионом строк.

На этом слайде пример продолжен и экземпляр принудительно останавливается, так как суперпользователь операционной системы (root) послал процессам postgres сигнал SIGQUIT (сигнал 3, см. kill -l). Получив такой сигнал, процессы экземпляра немедленно завершают свою работу без обычных действий при нормальном останове. То есть, этот сигнал для сервера PostgreSQL означает "выключение питания". Контрольная точка при таком выключении не выполняется.

Если бы долговечность (Durability) не обеспечивалась, то таблица, вероятно, пропала, так как до получения сигнала SIGQUIT контрольная точка (CHECKPOINT) могла и не выполняться. Контрольная точка к моменту ее завершения заканчивает копирование всех страниц из кеша буферов, которые оказались грязными на момент ее старта. После такого ненормального завершения процессов экземпляра, он был запущен и выполненная команда подсчета количества строк в таблице подтвердила, что миллион строк на своем месте.

Долговечность в PostgreSQL гарантируется журналом предзаписи WAL, который гарантирует, что изменения, зафиксированные транзакцией, не пропадут в результате сбоя.

Внимание! Не используйте сигнал SIGKILL (9 сигнал).

См. man postgres.

Номера транзакций

student@student=> BEGIN;
BEGIN
student@student=*> SELECT pg_current_xact_id();
pg_current_xact_id
--------------------
800
(1 строка)
student@student=*> COMMIT;
COMMIT
  • Транзакции уникально идентифицируются с помощью XID.
  • Номера XID выделяются последовательно, определяют "время" базы данных.
  • XID с меньшими номерами считаются "более ранними", счетчик циклический.
  • В Pangolin номера транзакций 64-разрядные (в обычном PostgreSQL 32-разрядные).
  • 64-разрядный номер строится искусственно на базе 32-разрядного смещения.

Механизм MVCC требует отличия одной транзакции от другой, версий строк, входящих и не входящий в тот или иной снимок данных и т.д. Все это базируется на номерах транзакций XID.

Номера транзакций выдаются последовательно и представлены с помощью беззнакового целого. Меньшие номера транзакций соответствуют более ранним транзакциям, а большие - более поздним. В обычном PostgreSQL используются 32-разрядные счетчики транзакций. Однако такое небольшое общее количество номеров транзакций оказывается недостаточным в промышленных системах и приводит к проблеме зацикливания счетчика транзакций. Дело в том, что каждая следующая транзакция требует увеличения счетчика и он рано или поздно достигает значения 232-1 (тридцать две единицы). Следующая более поздняя транзакция будет с номером 0, так как 33-го разряда нет. Для предотвращения этого используется технология заморозки, которая будет объяснена в главе "Обслуживание СУБД".

В Pangolin счетчики транзакций сделаны 64-разрядными, при использовании которых зацикливание счетчика транзакций маловероятно. Такая разрядность счетчика сделана искусственно: на каждой странице данных записывается 32-битное смещение, которое будучи добавлено к обычным номерам транзакций и дает 64 разряда. В примере на слайде получен номер текущей транзакции. Для этого была использована функция pg_current_xact_id(). https://www.postgresql.org/docs/15/functions-info.html.

Многоверсионность

Версии строк

Разные версии строки отличаются друг от друга с помощью двух специальных полей, которые находятся в заголовке строк:

  • xmin - номер транзакции, создавшую эту версию строки (новые версии строк создают INSERT и UPDATE);
  • xmax - номер транзакции, удалившей эту версию строки (удаляют версии UPDATE и DELETE).

Оба этих поля 32-х разрядные. Иметь поля xmin и xmax 64-х разрядными будет слишком накладно, поэтому в Pangolin и используется 32-х битное смещение, записанное в специальное место на каждой странице отношения. Таким образом, на одной странице могут находиться версии строк, созданные или удаленные транзакциями с номерами, отличающимися не более, чем на 232 транзакций.

Удаленные версии строк отличаются наличием номера транзакции, удалившей их, в поле xmin. Эти удаленные версии строк могут достаточно долго находиться на страницах, так как они могут присутствовать в каких-либо снимках данных. Иначе говоря, нужны каким-либо транзакциям. Если же эти версии строк не входят ни в один снимок данных, их следует очистить, освободив место на странице для вставки новых версий строк. Об очистке будет рассказано в главе 10.

INSERT создает строку, в поле xmin - 32-разрядная младшая часть XID.

UPDATE записывает в поле xmax номер транзакции и создает новую версию строки, в поле xmin которой стоит тот же номер.

DELETE записывает в поле xmax номер транзакции.

Удаленные версии строк физически не стираются до специальной очистки.

Снимки данных

Снимок данных - представление версий строк на определенный момент времени, необходимый для команд и транзакций. Время в СУБД определяется номерами транзакций. Меньшие номера соответствуют более ранним транзакциям, большие - более поздним (упрощение, не учитывающее переход через 0).

Снимок не является физической копией данных, он представлен набором чисел, идентифицирующим:

  • XID транзакции, которая была зафиксирована самой последней ДО создания снимка;
  • XID всех транзакций, которые УЖЕ были активными на момент создания снимка.

Те изменения данных (то есть, версии строк), которые были произведены еще пока не зафиксированными транзакциями (активными) НЕ должны быть видны в транзакции, для которой создан снимок. Транзакции, начавшиеся позже (имеющие большие XID) текущей транзакции, относятся для нее к "будущему" и их изменения в данных (версии строк с большими XID, чем у текущей транзакции) НЕ должны быть видны. Версии строк с xmax, меньшим, чем XID текущей транзакции, были удалены до того, как началась текущая транзакция. И если номер xmax строк соответствует зафиксированной транзакции, то в текущей транзакции эти удаленные версии строк не должны быть видны.

Правила видимости:

– изменения строк, произведенных в транзакции, видны в этой транзакции; – созданные и не удаленные зафиксированными транзакциями версии строк с xmin < XID видны в снимке; – удаленные зафиксированными транзакциями версии строк с xmax < XID не видны в снимке; – версии строк, созданные транзакциями, не зафиксированными на момент создания снимка, не видны; – версии строк, созданными транзакциями, стартовавшими после создания снимка, xmin > XID относятся к будущему и из настоящей транзакции не видны.

Уровни изоляции транзакций

Уровни изоляции транзакций определены стандартом SQL, но не все уровни реализованы в PostgreSQL.

Read Uncommitted, определенный стандартом SQL, не реализован в PostgreSQL, так как допускает аномалию грязного чтения - разрешает в других транзакциях видеть изменения, еще не зафиксированных текущей транзакцией.

Read Committed - уровень изоляции, принятый в PostgreSQL по умолчанию. На этом уровне перед каждой командой транзакции снимок строится заново. Соответственно, изменения, произведенные зафиксированными другими транзакциями, будут видны в текущей транзакции.

Repeatable Read - на этом уровне изоляции снимок данных строится ровно один раз - перед первой командой транзакции, и вся транзакция работает с этим снимком. При работе на таком уровне изоляции вполне вероятны ошибки сериализации из-за параллельной работы других транзакций.

Serializable - полная изоляция транзакций друг от друга, один снимок на транзакцию. Если действие транзакций невозможно строго упорядочить, возникает ошибка сериализации. https://www.postgresql.org/docs/15/transaction-iso.html.

Необходимость очистки

Удаленные версии строк (мертвые строки - dead tuples), не входящие ни в один снимок должны очищаться. Иначе они просто расходуют место на диске и приводят к раздуванию размеров файлов хранения данных. Очистку осуществляют в PostgreSQL следующими способами:

  • командой VACUUM - она стирает мертвые версии строк, освобождая место на страницах для вставки новых версий строк;
  • демоном autovacuum - это набор фоновых процессов, выполняющих очистку автоматически по мере накопления изменений в базах данных.

По умолчанию autovacuum в PostgreSQL включен и выключать его не следует даже в системах, ориентированных на архивную работу и не подразумевающих изменений данных.

Очистка не только очищает мертвые версии строк, но и строит слои fsm - карту свободного пространства, vm - карту видимости. Более того, очистка еще и отвечает за заморозку, предотвращая зацикливание счетчика транзакций.

Имеется также команда VACUUM FULL - она выполняет другую процедуру: полностью реорганизует страницы файла с данными, перенося их в новый файл данных уже без мертвых строк, уплотняя таким образом файл и уменьшая его размер. https://www.postgresql.org/docs/15/routine-vacuuming.html.

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

Удаленные версии строк, видимые в каких-либо снимках, очищать нельзя.

Если же удаленная версия строки не входит в какой-либо снимок, то ее следует очистить, высвободив место для новой версии строки.

Удаленные версии строк, не нужные в снимках, находятся за горизонтом очистки, который определяется конкретным XID.

Все удаленные версии строк за горизонтом подлежат очистке.

Долгие транзакции удерживают горизонт, мешая работе очистки, что приводит к излишнему расходованию пространства файловой системы.

Вручную очистку выполняет команда VACUUM, автоматическая очистка осуществляется с помощью autovacuum.

Блокировки

  • Блокировки строк:

    • чтение не блокирует строки;
    • изменение блокирует строку для других изменений, чтение разрешено.
  • Блокировки страниц:

  • для служебных операций, например, с индексами.

  • Блокировки таблиц:

    • запрещают изменение или удаление таблицы;
    • могут запрещать чтение таблицы, например, при ее перестроении.
  • Блокировки объектов в памяти.

  • Устанавливаются и снимаются автоматически, но можно управлять и вручную.

Помимо механизма MVCC PostgreSQL также использует и блокировки. Большинство блокировок относится к строкам. Процессов, читающих строки может быть множество и они не блокируют друг - друга. Более того, читающие процессы не блокируют пишущий, который в это же время может изменять данные в этой строке. Но другие претенденты на изменение этой строки должны получить соответствующую блокировку на эту строку. Если строка уже заблокирована пишущим процессом, то другой писатель должен дождаться освобождения блокировки.

Есть блокировки на уровне страниц - они используются операциями, связанными с работой индексов.

Блокировки таблиц также применяются. Так, например, при перестроении таблицы командой VACUUM FULL, блокируется вся таблица.

Еще одна разновидность блокировок - блокировки в памяти. https://www.postgresql.org/docs/15/mvcc-intro.html.

PostgreSQL автоматически управляет блокировками, однако есть инструменты для ручного управления ими. https://www.postgresql.org/docs/15/explicit-locking.html.

Состояние транзакций

  • Для каждой транзакции в памяти в массиве XACT сохраняется ее состояние посредством двух бит:

    – активная; – зафиксирована; – отменена.

  • Массив XACT для надежности сохраняется на диске в каталоге pg_xact.

  • Хранимые на диске данные в pg_xact называются commit log.

  • Данные о состоянии транзакций нужны для определения видимости строк.

  • Если в XACT транзакция не помечена как зафиксированная, изменяемые ей строки не видны.

В PostgreSQL имеется специальный механизм для отслеживания состояния транзакций: массив XACT. В нем для каждой транзакции имеется два бита:

  • признак фиксации транзакции;
  • признак отмены транзакции.

Эта информация важна для определения видимости версий строк в снимках. Поэтому она должна быть защищена от сбоя, что делается с помощью журнала предзаписи WAL. Более того, после сбоя информации о статусе транзакций влияет на процедуры восстановления. Поэтому статус записывается на диск в каталог pg_xact. https://www.postgresql.org/docs/15/storage-file-layout.html.

Итоги

  • Транзакции обеспечивают атомарность, согласованность, изоляцию и долговременность хранения.
  • Команды и транзакции работают со снимками данных, предоставляющими различные версии строк - MVCC.
  • Уровень изоляции транзакции определяет порядок построения снимков данных.
  • Реализованы три уровня изоляции транзакций.
  • Для определения видимости строк необходимо отслеживать состояние транзакций.
  • Кроме MVCC параллельную работу с данными обеспечивают блокировки.