how/MVCC
MVCC: одна строка, две версии
UPDATE в PostgreSQL никогда не перезаписывает строку. Он пишет новую версию, а старую штампует транзакцией, которая её устарила. Какое-то время одна строка существует на странице дважды, и две сессии могут читать две разные правды. Вот как, и как потом убирает VACUUM.
Первое, что удивляет в PostgreSQL: UPDATE не меняет строку на месте. Он пишет новую версию строки, а старую оставляет на странице, пометив устаревшей. То же и с DELETE (новой версии нет, только пометка), и с каждой строкой, которую создаёт INSERT.
Это MVCC, многоверсионное управление конкурентным доступом. У каждой версии в заголовке (см. tuple-header) два транзакционных штампа:
xmin- транзакция, которая эту версию создала;xmax- транзакция, которая её устарила (обновила, удалила или заблокировала).xmax = 0значит, что версия ещё жива.
Что транзакции можно увидеть, решает её снимок (см. snapshot): замороженный взгляд на то, какие транзакции были зафиксированы на момент старта. Одни и те же байты на диске разные снимки видят по-разному - именно поэтому читатели не блокируют писателей. Нажми play и проследи одну строку от единственной версии до вычищенной страницы.
§ шаги
Начнём с единственной строки в таблице
mv, вставленной транзакцией 786:sqlSELECT xmin, xmax, ctid, * FROM mv;
-- xmin | xmax | ctid | id | note
-- ------+------+-------+----+------
-- 786 | 0 | (0,1) | 1 | a
Одна версия, по адресу
ctid (0,1). Еёxminравен 786 (транзакция, которая её создала), аxmaxравен 0 - значит никто её не устарил: строка жива. Это и видит каждый читатель.
итого
Что важно запомнить:
UPDATEпод капотом - это delete плюс insert: новая версия со свежимxminи старая версия со штампомxmax = <txid обновляющей>. Обе живут на странице до уборки. Зачем так - в why-mvcc.- Видимость вычисляется, а не хранится. Версия видна, если её
xminзафиксирован и попадает в твой снимок, аxmaxпуст или ещё не виден тебе. Полные правила - в xmin-xmax. - Два снимка могут законно читать два разных значения одной строки в один и тот же момент по часам. Ничего не сломано - они смотрят сквозь разные срезы времени.
- Версия, которую больше не видит ни один живой снимок, - это мёртвый кортеж. Он всё ещё занимает место на странице, пока VACUUM не уберёт его и не освободит указатель (см. vacuum).
- VACUUM может убрать только версии старше самого старого работающего снимка, горизонта транзакций (см. transaction-horizon). Одна долгая транзакция держит горизонт и копит мёртвые кортежи по всей базе - отсюда и берётся раздувание таблиц.
- Когда обновление влезает на ту же страницу и ни один индекс не покрывает изменённую колонку, Postgres делает HOT-обновление (см. hot-updates) и сцепляет версии, не трогая индексы.
Выигрыш: читатели не блокируют писателей, писатели не блокируют читателей. Цена - мёртвые кортежи, и собрать их - вся работа VACUUM.
§ копнуть в базу знаний
- why-mvccзачем MVCC - читатели и писатели, которые не блокируют друг друга
- xmin-xmaxxmin и xmax - правила видимости
- snapshotснимок - замороженный взгляд на зафиксированные транзакции
- hot-updatesHOT-обновления - цепочки версий в обход индекса
- vacuumVACUUM - сборщик мёртвых кортежей
- transaction-horizonгоризонт транзакций - как далеко назад чистит VACUUM