Углубленный анализ семейства PostgreSQLОрганизовано изThe Internals of PostgreSQLПодождите, пока серия статей, от фрагментации систематизированного обучения, более глубокого проникновения в базу данных, касаясь обхода, взаимного подтверждения, а также облегчения других реляционных баз данных, таких как MySQL или база данных NOSQL.
Углубленный анализ управления параллелизмом и механизма транзакций в серии PostgreSQL.
Контроль параллелизма направлен на обеспечение согласованности и изоляции в ACID для сценариев, в которых транзакции распараллелены в базе данных. Тремя основными технологиями управления параллелизмом в технологии баз данных являются: многоверсионный контроль параллелизма (MVCC), строгая двухфазная блокировка (S2PL) и оптимистичный контроль параллелизма (OCC), каждая из которых имеет множество вариантов. В MVCC каждая операция записи создает новую версию поверх старой версии и сохраняет старую версию. Когда транзакции необходимо прочитать данные, система базы данных выберет из всех версий ту версию, которая соответствует требованиям уровня изоляции транзакций. Самым большим преимуществом MVCC является то, что чтение не блокирует запись, а запись не блокирует чтение; в то время как в системе, подобной S2PL, транзакции записи заранее приобретут эксклюзивные блокировки, которые заблокируют транзакции чтения.
РСУБД, такие как PostgreSQL и Oracle, на самом деле используют вариант метода MVCC, называемый Snapshot Isolation (SI). Oracle представила дополнительные сегменты отката: при записи новых данных старая версия данных будет записана в сегмент отката, а затем перезаписана в фактический блок данных. PostgreSQL использует относительно простую реализацию, и новые объекты данных будут вставлены непосредственно в связанную страницу таблицы, а при чтении данных таблицы PostgreSQL выберет соответствующую версию.
SI может избежать трех аномалий, определенных в стандарте ANSI SQL-92: грязное чтение, неповторяемое чтение и фантомное чтение; изоляция сериализуемых моментальных снимков (SSI) может обеспечить настоящие возможности последовательного чтения и записи.
Isolation Level | Dirty Reads | Non-repeatable Read | Phantom Read | Serialization Anomaly |
---|---|---|---|---|
READ COMMITTED | Not possible | Possible | Possible | Possible |
REPEATABLE READ | Not possible | Not possible | Not possible in PG; See Section 5.7.2. (Possible in ANSI SQL) | Possible |
SERIALIZABLE | Not possible | Not possible | Not possible | Not possible |
Структура кортежа
Transaction ID
Когда транзакция включена, встроенный диспетчер транзакций POSTGRESQL присвоит ей уникальный идентификатор транзакции (TXID); TXID — это 32-битное целочисленное значение, не имеющее типа, которое можетtxid_current()
Функция для получения текущего txid:
testdb=# BEGIN;
BEGIN
testdb=# SELECT txid_current();
txid_current
--------------
100
(1 row)
PostgreSQL также резервирует три ключевых значения txid для специальной маркировки: 0 представляет собой недопустимый txid, 1 представляет собой txid при запуске, который используется только при запуске кластера баз данных; 2 представляет собой замороженный (Frozen) txid, используемый в используемой сериализации. во время бизнеса. PostgreSQL выбирает числовой тип txid для удобства сравнения; для транзакций со значением txid 100 все транзакции меньше 100 произошли в прошлом и являются видимыми, а все транзакции больше 100 произошли в будущем, то есть невидимы.
Поскольку количество txid, требуемых в реальной системе, может превышать максимально допустимое, PostgreSQL фактически рассматривает эти txid как кольца.
HeapTupleHeaderData
Кортежи кучи на страницах таблиц часто состоят из трех частей: структуры HeapTupleHeaderData, растрового изображения NULL и пользовательских данных.
Свойства HeapTupleHeaderData, тесно связанные с обработкой транзакций:
- (TransactionId)t_xmin: сохранить txid при вставке кортежа
- (TransactionId)t_xmax: сохранить txid при удалении или обновлении кортежа. Если он не был обновлен или удален, установите для него значение 0, что означает, что он недействителен.
- (CommandId)t_cid: хранит идентификатор команды, то есть количество всех команд SQL, выполненных в рамках транзакции командой, создавшей кортеж; например
BEGIN; INSERT; INSERT; INSERT; COMMIT;
Эта транзакция, если это Tuple, созданный первой командой INSERT, то ее значение t_cid равно 0, а второе — 1 - (ItemPointerData)t_ctid: когда кортеж обновляется, значение указывает на вновь созданный кортеж, в противном случае оно указывает на себя
Вставка, удаление и обновление кортежа
Как упоминалось выше, кортежи на страницах таблиц располагаются следующим образом:
вставлять
При операции вставки PostgreSQL направит новый кортеж страницы, вставленный в целевую таблицу:
Если транзакция с txid 99 вставляет новый кортеж, в поле заголовка кортежа будет установлено следующее значение:
- t_xmin согласуется с txid транзакции, создавшей кортеж, который равен 99.
- t_xmax установлен на 0, потому что он не был удален или обновлен
- t_cid имеет значение 0, поскольку Tuple был создан первой командой Insert в транзакции.
- t_ctid установлен на
(0, 1)
, указывая на себя
testdb=# CREATE EXTENSION pageinspect;
CREATE EXTENSION
testdb=# CREATE TABLE tbl (data text);
CREATE TABLE
testdb=# INSERT INTO tbl VALUES('A');
INSERT 0 1
testdb=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid
FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
1 | 99 | 0 | 0 | (0,1)
Удалить
В операции удаления целевой кортеж будет логически удален первым, то есть значение t_xmax устанавливается равным значению txid транзакции, которая в данный момент удаляет кортеж.
После того, как транзакция будет зафиксирована, PostgreSQL пометит кортеж как Dead Tuple, а затем будет полностью очищен во время обработки VACUUM.
возобновить
Во время операции обновления PostgreSQL сначала логически удалит последний кортеж, а затем вставит новый:
Строка, показанная на рисунке выше, была вставлена транзакцией с txid 99 и дважды подряд обновлена транзакцией с txid 100; после фиксации транзакции Tuple_2 и Tuple_3 будут помечены как Dead Tuples.
Free Space Map
При вставке кортежа кучи или кортежа индекса PostgreSQL использует FSM связанной таблицы, чтобы решить, какую страницу следует выбрать для конкретной операции вставки. Каждый FSM хранит информацию об оставшейся емкости пространства, относящейся к таблице или индексному файлу, которую можно просмотреть следующими способами:
testdb=# CREATE EXTENSION pg_freespacemap;
CREATE EXTENSION
testdb=# SELECT *, round(100 * avail/8192 ,2) as "freespace ratio"
FROM pg_freespace('accounts');
blkno | avail | freespace ratio
-------+-------+-----------------
0 | 7904 | 96.00
1 | 7520 | 91.00
2 | 7136 | 87.00
3 | 7136 | 87.00
4 | 7136 | 87.00
5 | 7136 | 87.00
....
Commit Log
PostgreSQL использует журнал фиксации, также известный как засорение, для хранения статуса транзакций; засоры хранятся в общей памяти и играют важную роль во всем жизненном цикле обработки транзакций. PostgreSQL определяет четыре разных состояния транзакции: IN_PROGRESS, COMMITTED, ABORTED и SUB_COMMITTED.
Clog состоит из нескольких страниц по 8 КБ в общей памяти, которая логически представляет собой структуру, подобную массиву.Нижний индекс массива — это txid связанной транзакции, а значение — статус текущей транзакции:
Если текущий txid превышает максимальный диапазон, который может содержать текущая страница засорения, PostgreSQL автоматически создаст новую страницу. Когда PostgreSQL остановлен или запущен процесс Checkpoint, данные засорения будут постоянно храниться в подкаталоге pg_xact с именами в порядке 0000, 0001, а максимальный размер одного файла — 256 КБ. При перезапуске PostgreSQL файлы, хранящиеся в каталоге pg_xact, будут перезагружены в память. При непрерывной работе PostgreSQL в засоре неизбежно будет скапливаться много устаревших или бесполезных данных, и эти бесполезные данные также будут очищаться при обработке Vacuum.
Снимок транзакции | Снимок транзакции
Снимок транзакций — это структура данных, в которой хранится информация о том, активны ли все текущие транзакции.Внутренне PostgreSQL представляет снимок в виде простой текстовой структуры.xmin:xmax:xip_list’
, например "100:100:", что означает, что все транзакции с txid меньше или равным 99 неактивны, а транзакции больше или равные 100 активны.
testdb=# SELECT txid_current_snapshot();
txid_current_snapshot
-----------------------
100:104:100,102
(1 row)
- xmin: самый ранний txid, который все еще находится в активном состоянии.Все более ранние транзакции находятся либо в видимом состоянии после фиксации, либо в приостановленном состоянии после отката.
- xmax: номер первой еще не выделенной транзакции Все транзакции с txid больше или равным этому значению еще не произошли относительно транзакции, которой принадлежит снимок, поэтому они невидимы.
- xip_list: txids, которые активны во время моментального снимка, будут включены только txids между xmin и xmax.
от100:104:100,102
Например, схематическая диаграмма выглядит следующим образом:
Моментальные снимки транзакций в основном предоставляются диспетчером транзакций (Диспетчер транзакций).На уровне изоляции READ COMMITTED транзакция будет назначена снимку независимо от того, выполняется ли команда SQL.Для транзакций на уровне изоляции REPEATABLE READ или SERIALIZABLE Только при выполнении первого оператора SQL он назначается моментальному снимку транзакции для обнаружения видимости. Значение моментальных снимков транзакций заключается в том, что когда моментальный снимок выполняет оценку видимости, независимо от того, была ли целевая транзакция зафиксирована или отменена, пока она помечена как активная в моментальном снимке, она будет рассматриваться как транзакция в состоянии IN_PROGRESS.
Менеджер транзакций всегда сохраняет информацию о текущих операциях. Предполагая, что три сделки начинают одно место, а уровень изоляции транзакций_а и транзакции_b прочитан преданный, уровень изоляции транзакции_c является повторяемым чтением.
-
Т1:
- Transaction_A запускает и выполняет первую команду SELECT. При выполнении первой команды Transaction_A запрашивает txid и моментальный снимок на данный момент. В этом случае диспетчер транзакций назначает txid 200 и возвращает моментальный снимок транзакции «200:200:».
-
Т2:
- Transaction_B запускает и выполняет первую команду SELECT. Диспетчер транзакций присваивает txid 201 и возвращает моментальный снимок транзакции «200:200:», поскольку выполняется Transaction_A (txid 200). Следовательно, Transaction_A нельзя увидеть из Transaction_B.
-
Т3:
- Transaction_C запускает и выполняет первую команду SELECT. Диспетчер транзакций присваивает txid 202 и возвращает моментальный снимок транзакции «200:200:», поэтому Transaction_A и Transaction_B не видны из Transaction_C.
-
Т4:
- Транзакция_A отправлена. Менеджер транзакций удаляет информацию об этой транзакции.
-
Т5:
- Transaction_B и Transaction_C выполняют соответствующие команды SELECT.
- Transaction_B требует моментального снимка транзакции, поскольку он находится на уровне READ COMMITTED. В этом случае транзакция_B получает новый снимок «201:201:», поскольку транзакция_A (txid 200) зафиксирована. Поэтому Transaction_B больше не является невидимой в Transaction_B.
- Transaction_C не нуждается в моментальном снимке транзакции, поскольку он находится на уровне REPEATABLE READ и использует полученный моментальный снимок, т. е. «200:200:». Поэтому Transaction_A по-прежнему невидима для Transaction_C.
Проверка видимости | Проверка видимости
Правила |Правила определения видимости
Правила обнаружения видимости используются для определения того, является ли кортеж видимым для транзакции, на основе t_xmin и t_xmax кортежа, засорения и моментального снимка транзакции, для которого он выделен.
Статус транзакции, соответствующей t_xmin, ABORTED
Когда состояние транзакции, соответствующее значению t_xmin кортежа, равно ABORTED, кортеж никогда не отображается:
/* t_xmin status = ABORTED */
// Rule 1: If Status(t_xmin) = ABORTED ⇒ Invisible
Rule 1: IF t_xmin status is 'ABORTED' THEN
RETURN 'Invisible'
END IF
Статус транзакции, соответствующей t_xmin, — IN_PROGRESS.
Для кортежей, которые не связаны с транзакциями, отличными от транзакции, вставившей кортеж, кортеж никогда не виден; он виден только для кортежей, принадлежащих к той же транзакции, что и кортеж (в настоящее время кортеж не удаляется и не обновляется). .
/* t_xmin status = IN_PROGRESS */
IF t_xmin status is 'IN_PROGRESS' THEN
IF t_xmin = current_txid THEN
// Rule 2: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax = INVAILD ⇒ Visible
Rule 2: IF t_xmax = INVALID THEN
RETURN 'Visible'
// Rule 3: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax ≠ INVAILD ⇒ Invisible
Rule 3: ELSE /* this tuple has been deleted or updated by the current transaction itself. */
RETURN 'Invisible'
END IF
// Rule 4: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin ≠ current_txid ⇒ Invisible
Rule 4: ELSE /* t_xmin ≠ current_txid */
RETURN 'Invisible'
END IF
END IF
Статус транзакции, соответствующей t_xmin, — COMMITTED.
В этот момент в кортеже они видны в большинстве случаев, подлежат обновлению или удалению вдобавок к кортежу.
/* t_xmin status = COMMITTED */
IF t_xmin status is 'COMMITTED' THEN
// If Status(t_xmin) = COMMITTED ∧ Snapshot(t_xmin) = active ⇒ Invisible
Rule 5: IF t_xmin is active in the obtained transaction snapshot THEN
RETURN 'Invisible'
// If Status(t_xmin) = COMMITTED ∧ (t_xmax = INVALID ∨ Status(t_xmax) = ABORTED) ⇒ Visible
Rule 6: ELSE IF t_xmax = INVALID OR status of t_xmax is 'ABORTED' THEN
RETURN 'Visible'
ELSE IF t_xmax status is 'IN_PROGRESS' THEN
// If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax = current_txid ⇒ Invisible
Rule 7: IF t_xmax = current_txid THEN
RETURN 'Invisible'
// If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax ≠ current_txid ⇒ Visible
Rule 8: ELSE /* t_xmax ≠ current_txid */
RETURN 'Visible'
END IF
ELSE IF t_xmax status is 'COMMITTED' THEN
// If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) = active ⇒ Visible
Rule 9: IF t_xmax is active in the obtained transaction snapshot THEN
RETURN 'Visible'
// If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) ≠ active ⇒ Invisible
Rule 10: ELSE
RETURN 'Invisible'
END IF
END IF
END IF
Процесс проверки видимости
Возьмем в качестве примера простое обновление и запрос с двумя транзакциями:
Уровень изоляции транзакции txid 200 на приведенном выше рисунке — READ COMMITED, а уровень изоляции txid 201 — READ COMMITED или REPEATABLE READ.
- При выполнении команды SELECT в момент времени T3:
Согласно правилу 6, в настоящее время толькоTuple_1
находится в видимом состоянии:
# Rule6(Tuple_1) ⇒ Status(t_xmin:199) = COMMITTED ∧ t_xmax = INVALID ⇒ Visible
testdb=# -- txid 200
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)
testdb=# -- txid 201
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)
- При выполнении команды select на T5:
Для транзакции txid 200, согласно правилу 7 и правилу 2,Tuple_1
видимый иTuple_2
Невидимый:
# Rule7(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = IN_PROGRESS ∧ t_xmax:200 = current_txid:200 ⇒ Invisible
# Rule2(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 = current_txid:200 ∧ t_xmax = INVAILD ⇒ Visible
testdb=# -- txid 200
testdb=# SELECT * FROM tbl;
name
------
Hyde
(1 row)
А для транзакции txid 201,Tuple_1
видно,Tuple_2
невидим:
# Rule8(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = IN_PROGRESS ∧ t_xmax:200 ≠ current_txid:201 ⇒ Visible
# Rule4(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 ≠ current_txid:201 ⇒ Invisible
testdb=# -- txid 201
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)
- При выполнении команды SELECT в момент времени T7:
Если транзакция с txid 201 в это время находится на уровне изоляции READ COMMITED, то txid 200 будет рассматриваться как COMMITTED, потому что моментальный снимок транзакции, полученный в это время,201:201:
,следовательноTuple_1
невидим, иTuple_2
видно:
# Rule10(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) ≠ active ⇒ Invisible
# Rule6(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ t_xmax = INVALID ⇒ Visible
testdb=# -- txid 201 (READ COMMITTED)
testdb=# SELECT * FROM tbl;
name
------
Hyde
(1 row)
Если TXID 201 в это время транзакции является повторяющийся уровень изоляции чтения, на этот раз, чтобы получить снимок или транзакционную200:200:
, то транзакция с txid 200 должна рассматриваться как состояние IN_PROGRESS, поэтому в настоящее времяTuple_1
видно, иTuple_2
невидим:
# Rule9(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) = active ⇒ Visible
# Rule5(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ Snapshot(t_xmin:200) = active ⇒ Invisible
testdb=# -- txid 201 (REPEATABLE READ)
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
(1 row)
Предотвращение потери обновлений | Предотвращение потери обновлений
Так называемый Lost Update, или ww-конфликт, возникает, когда две транзакции одновременно обновляют одну и ту же строку; в PostgreSQL оба уровня REPEATABLE READ и SERIALIZABLE должны избегать этой аномалии.
(1) FOR each row that will be updated by this UPDATE command
(2) WHILE true
/* The First Block */
(3) IF the target row is being updated THEN
(4) WAIT for the termination of the transaction that updated the target row
(5) IF (the status of the terminated transaction is COMMITTED)
AND (the isolation level of this transaction is REPEATABLE READ or SERIALIZABLE) THEN
(6) ABORT this transaction /* First-Updater-Win */
ELSE
(7) GOTO step (2)
END IF
/* The Second Block */
(8) ELSE IF the target row has been updated by another concurrent transaction THEN
(9) IF (the isolation level of this transaction is READ COMMITTED THEN
(10) UPDATE the target row
ELSE
(11) ABORT this transaction /* First-Updater-Win */
END IF
/* The Third Block */
ELSE /* The target row is not yet modified or has been updated by a terminated transaction. */
(12) UPDATE the target row
END IF
END WHILE
END FOR
В приведенном выше процессе команда UPDATE проходит через каждую строку, которую нужно обновить, и когда обнаруживает, что строка обновляется другими транзакциями, она переходит в состояние ожидания, пока строка не будет разблокирована. Если строка уже была обновлена и уровень изоляции REPEATABLE или SERIALIZABLE, отмените обновление.
Обновление означает, что строка была обновлена другой параллельной транзакцией, и ее транзакция еще не завершена. Поскольку SI Postgres Pro использует схему «выиграл первый обновляющий», в этом случае текущая транзакция должна дождаться завершения транзакции, обновившей целевую строку. Предположим, что транзакции Tx_A и Tx_B выполняются одновременно, и Tx_B пытается обновить строку, но Tx_A обновил ее и все еще выполняется, а Tx_B ожидает завершения Tx_A. Продолжает операцию обновления текущей транзакции после обновления транзакции, зафиксированной целевой строкой. Если текущая транзакция находится на уровне READ COMMITTED, целевая строка будет обновлена; в противном случае REPEATABLE READ или SERIALIZABLE текущая транзакция немедленно прерывается, чтобы предотвратить потерю обновлений.
космическая организация
Механизм управления параллелизмом PostgreSQL также зависит от следующих процессов обслуживания:
- Удалены кортежи и индексные кортежи, помеченные как мертвые
- Удалить устаревшие части засора
- Заморозить старые txids
- Обновите FSM, VM и другую статистику
Давайте сначала обсудим проблему циклической обработки txid, предполагая, что транзакция txid 100 вставляет определенныйTuple_1
, значение t_xmin, соответствующее Tuple, равно 100, тогда сервер работает долго,Tuple_1
Период не изменен. пока txid не будет2^31 + 101
, для транзакции, когда она выполняет команду SELECT, она не может видетьTuple_1
, т.к. транзакция с txid 100 происходит в будущем относительно него, созданный ею Tuple естественно невидим.
Чтобы решить эту проблему, PostgreSQL представил так называемый замороженный TXID (замороженный TXID) и настроил процесс замораживания для конкретно справляться с проблемой. Как упомянуто выше, TXID 2 является зарезервированным значением, которое конкретно представляет эти замороженные кортежи, которые всегда неактивны и видны. Процесс замораживания также называется равномерноми процессом вакуума. Он сканирует все таблицы файлов и устанавливает поле T_XMIN кортеж, чья разница от текущего TXID превышает определение Vacuum_Freeze_min_Age на 2. После версии 9.4 бит XMIN_FROZEN в поле T_INFOMASK устанавливается, чтобы указать, что кортеж находится в замороженном состоянии.
дальнейшее чтение
-
Если вы хотите пойти глубжеРаспределенные системы, распределенные вычисления, распределенное хранилище, базы данных, операционные системы, виртуализацияд., вы можете обратиться кГлубокое понимание распределенной инфраструктуры,DistributedSystem CheatSheet, Database CheatSheet, Linux CheatSheet, MySQL CheatSheet, Docker CheatSheet, Flink CheatSheet, Kafka CheatSheetЖдать.
-
Если вы хотите получить дополнительную информацию о распределенных системах, планировании виртуализации, базах данных, распределенном хранилище, распределенных вычислениях, операционных системах и т. д.:Docker List, Kubernetes List, Linux List, HTTP List, Distributed System List, Blockchain List, Flink List, Kafka List, Database List, MySQL List, PostgreSQL List, etc.