У PostgreSQL три слоя блокировок под разные задачи и время жизни.
| Слой | Защищает | Живёт | Видно |
|---|---|---|---|
| Тяжёлые | таблицы, строки | до конца транзакции | pg_locks |
| LWLock | структуры в shared memory | время операции | wait_event |
| Spinlock | пара полей | десятки тактов | нигде |
LWLock
Лёгкая блокировка охраняет конкретную структуру в разделяемой памяти:
буфер кеша при чтении (см. buffer-cache), буфер WAL при вставке,
таблицу хешей. Два режима
(разделяемый/исключительный), но в отличие от тяжёлых - держится
микросекунды, не имеет детектора deadlock (берётся в фиксированном
порядке, цикл невозможен) и не попадает в pg_locks. Имя говорит о
цели: WALInsert, BufferContent, LockManager.
Spinlock
Защищает буквально пару инструкций. Не усыпляет процесс - крутится в busy-wait, пока замок занят. Оправдан только при микроскопическом времени удержания: переключение контекста стоило бы дороже. Нигде не учитывается.
Wait events
Раз LWLock и тяжёлые ожидания не сводятся в одну таблицу, общий язык -
wait_event_type/wait_event в pg_stat_activity:
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity WHERE state = 'active'
GROUP BY 1, 2 ORDER BY count(*) DESC;
Lock - тяжёлая блокировка, LWLock - лёгкая, IO - диск, пустой тип
- работа на CPU. Узкое место ищут семплированием: снять много раз и посмотреть, что чаще.
Предикатные блокировки (SIReadLock)
Их использует Serializable/SSI (см. serializable-ssi), и они не блокируют. При чтении на
Serializable транзакция оставляет метку SIReadLock - «я это читала».
Никто не ждёт. Но если другая транзакция пишет туда, где стоит чужая
метка, и шаблон зависимостей опасен, при коммите будет ошибка
сериализации 40001. Огрубляются кортеж → страница → таблица ради
памяти, ценой ложных срабатываний.
SELECT count(*) FROM pg_locks WHERE mode = 'SIReadLock';
В отличие от deadlocks, конфликт SSI всплывает не циклом, а на коммите. Тяжёлый слой описан в relation-locks.