awesome-everything EN
↑ Обратно к восхождению

Базы данных

HOT-обновления и уровни изоляции

Суть HOT экономит 30–70% WAL когда UPDATE не трогает индексированные колонки и новый tuple влезает на ту же страницу; fillfactor резервирует место для цепочки. Четыре уровня изоляции от RC до Serializable — выбор и цена каждого.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 18 min

UPDATE обновляет неиндексированную колонку. Postgres пишет ноль новых индексных записей. WAL сокращается на 30–70%. Это называется HOT — и ты никогда этого не видишь, пока не узнаешь, куда смотреть.

HOT-обновления

Если UPDATE не трогает ни одну индексированную колонку и новый tuple влезает на ту же страницу, что и старый, Postgres выполняет heap-only tuple (HOT) обновление:

  • Старый tuple получает флаг HEAP_HOT_UPDATED («иди по цепочке t_ctid»)
  • Новый tuple получает флаг HEAP_ONLY_TUPLE («на меня не указывает ни один индекс»)
  • Индекс продолжает указывать на исходный tuple, цепочка в heap доводит lookup до конца

Результат: ноль новых индексных записей. На UPDATE-нагруженных воркфлоу, где большинство колонок неиндексированы — это разница между удвоением WAL и его едва заметным ростом.

Условия HOT:

УсловиеЕсли не выполнено
UPDATE не трогает индексированную колонкуНужна новая индексная запись — HOT невозможен
Новый tuple влезает на ту же heap-страницуЦепочка ломается; следующий UPDATE снова платит цену индекса

Fillfactor (ALTER TABLE foo SET (fillfactor = 85)) резервирует процент каждой heap-страницы под HOT-обновления. По умолчанию 100 — для UPDATE-нагруженных таблиц типично 70–90.

Мониторинг: pg_stat_all_tables.n_tup_hot_upd / n_tup_upd — HOT-доля. Выше 80% хорошо; ниже 50% — или слишком агрессивный fillfactor, или неправильный набор индексов.

Четыре уровня изоляции

УровеньSnapshotDirty readNon-rep. readPhantomWrite skew
Read UncommittedPer-statement— (Postgres блокирует)ДаДаДа
Read CommittedPer-statementНетДаДаДа
Repeatable ReadPer-transactionНетНетНетДа
SerializablePer-transaction + SSIНетНетНетНет

Read Committed (по умолчанию) — каждый statement получает свежий snapshot. Два SELECT в одной транзакции могут увидеть разные данные.

Repeatable Read — единственный snapshot взят на первом statement. Postgres реализует RR как snapshot isolation — строже стандартного RR (phantom read запрещён), но write skew разрешён.

Serializable (с 9.1) — добавляет SSI поверх RR. Postgres отслеживает зависимости read–write и откатывает транзакции с SQLSTATE 40001, чей порядок коммита не соответствует ни одному серийному расписанию.

Write skew: аномалия, которую ловит только Serializable

Проследи
1/6

Проследи аномалию write skew под Repeatable Read, которую Serializable предотвратил бы.

1
Step 1 of 6
Сетап: таблица doctors с булевым on_call. Двое: alice и bob, оба on_call=true. Инвариант: хотя бы один врач должен быть on_call всегда.
2
Locked
T1 (alice снимает смену): SELECT COUNT(*) FROM doctors WHERE on_call = true. Возвращает 2.
3
Locked
T2 (bob снимает смену, параллельно с T1): тот же SELECT. Тоже возвращает 2.
4
Locked
T1: UPDATE doctors SET on_call = false WHERE name = 'alice'. Коммитит.
5
Locked
T2: UPDATE doctors SET on_call = false WHERE name = 'bob'. Коммитит.
6
Locked
Что произошло бы под Serializable?

Что должно делать приложение

Под Read Committed приложение само отвечает за предотвращение lost update:

  • SELECT ... FOR UPDATE (пессимистичная блокировка строки)
  • UPDATE ... WHERE version = ? с оптимистичной конкуренцией
  • INSERT ... ON CONFLICT ... DO UPDATE для upsert

Под Repeatable Read и Serializable база поднимает SQLSTATE 40001 при конфликте. Единственный корректный ответ — повторить всю транзакцию.

Когда выбирать какой уровень

Read Committed — дефолт по причине: на 90% бизнес-логики даёт ожидаемую семантику и максимальный throughput, потому что snapshot перевычисляется на каждом statement.

Repeatable Read — когда логика опирается на стабильность набора строк между несколькими SELECT (например, отчёт суммирует поля, которые потом сверяет с агрегированной версией).

Serializable — когда у бизнес-логики есть инвариант, который сохраняется только при отсутствии write skew: на стороне финтеха «хотя бы один счёт остаётся включённым», в медицине «хотя бы один врач остаётся on-call», в инвентаре «никакая позиция не получает больше резервов, чем имеет на складе». Serializable не магия — он добавляет до 5x cost и предполагает готовность к ретраям.

Прикладные паттерны под Read Committed

SELECT ... FOR UPDATE на горячих строках — самый предсказуемый способ избежать lost update: блокировка эксклюзивная, держится до конца транзакции. Порядок взятия должен быть детерминистическим (например, по ascending id) во избежание deadlock.

Оптимистичная конкуренция через колонку version (или updated_at): SELECT читает версию, UPDATE проверяет её в WHERE и атомарно увеличивает; если строка не вернулась — повтор. Hibernate @Version, Sequelize versionAttribute, ActiveRecord optimistic_locking.

INSERT ... ON CONFLICT ... DO UPDATE (upsert) — атомарно решает гонку вставка-vs-обновление без явных блокировок, но требует уникального constraint.

Числа

HOT и изоляция: ключевые числа
Экономия WAL на HOT
30–70% на UPDATE-нагрузке
Уровень изоляции по умолчанию
READ COMMITTED
Код serialization failure
SQLSTATE 40001
HOT-доля здорово
>80% n_tup_hot_upd / n_tup_upd
Типичный fillfactor
70–90 для UPDATE-нагруженных таблиц
Serializable overhead
до 5x cost при высокой конкуренции

Проверь себя

Викторина

HOT-обновление (heap-only tuple) может произойти, когда...

Викторина

Write skew — это аномалия, разрешённая под Repeatable Read. Почему?

Викторина

Postgres логирует снижение HOT-доли (n_tup_hot_upd / n_tup_upd) ниже 40% на таблице orders. Каков первый шаг диагностики?

Вспомните перед уходом
  1. 01
    Назови два условия, при которых Postgres выполняет HOT-обновление, и объясни, что происходит при нарушении каждого.
  2. 02
    Почему Postgres иногда откатывает транзакцию под Serializable с SQLSTATE 40001, даже если она успешно прошла бы под Repeatable Read?
  3. 03
    Сервис переводов банка хочет атомарно перевести деньги между счетами. Какой уровень изоляции ты выберешь и почему?
Recap
  • HOT: UPDATE без изменения индексированных колонок + новый tuple на той же странице = ноль новых индексных записей, WAL -30–70%
  • Fillfactor резервирует место для HOT-цепочек; мониторинг — n_tup_hot_upd / n_tup_upd
  • RC: snapshot per-statement. RR: snapshot per-transaction. Serializable: RR + SSI (отслеживание rw-антизависимостей)
  • Write skew разрешена под RR, запрещена под Serializable
  • RC — дефолт и лучший выбор для большинства нагрузок; Serializable — когда нужен инвариант над набором строк
  • Под RC защита от lost update — ответственность приложения: FOR UPDATE, оптимистичная конкуренция или upsert
Связанные уроки
встречается в140
Продолжить восхождение ↑VACUUM, bloat и autovacuum
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.