Зачем нужны блокировки?
Когда несколько транзакций одновременно работают с одними и теми же данными, без координации быстро начинаются проблемы. Самый простой пример — две транзакции читают одно значение, обе его меняют и записывают обратно. В итоге одно изменение теряется.
Блокировки в PostgreSQL — это способ определить, кто и когда имеет право что-то делать с данными. Если ресурс уже занят, следующая транзакция должна ждать. В большинстве случаев блокировки почти не мешают системе работать параллельно. Основную конкуренцию PostgreSQL решает через MVCC, а блокировки подключаются там, где без них не обойтись.
На практике чаще всего приходится сталкиваться с двумя уровнями: блокировки таблиц и блокировки строк. Есть ещё внутренние механизмы синхронизации, но они больше интересны при разборе производительности, чем в обычной разработке.
Блокировки таблиц
Любая операция с таблицей почти всегда сопровождается табличной блокировкой. Обычно это происходит автоматически
и незаметно, пока не начинается конкуренция. Например, обычный SELECT берёт самый слабый
режим — ACCESS SHARE. Он ни с кем не конфликтует, поэтому читать одну и ту же таблицу могут сколько
угодно транзакций одновременно.
Когда начинается изменение данных (INSERT, UPDATE, DELETE), используется
ROW EXCLUSIVE. Несмотря на название, это не означает, что таблица заблокирована для других.
Параллельные изменения возможны, просто появляются ограничения на более тяжёлые операции.
Служебные операции, например, VACUUM или CREATE INDEX CONCURRENTLY
используют SHARE UPDATE EXCLUSIVE. Он не мешает обычной работе с данными, но не даёт выполнять
операции, которые могут конфликтовать на уровне структуры таблицы.
Самый жёсткий вариант — ACCESS EXCLUSIVE. Его берут, например, ALTER TABLE или
DROP TABLE. В этот момент таблица полностью недоступна ни для чтения, ни для записи.
Блокировки строк
Если есть задача работать с конкретными записями, а не с таблицей целиком, используются блокировки строк. Они
появляются либо неявно (например, при UPDATE), либо явно через
SELECT ... FOR UPDATE (и т.п.).
Типы блокировок строк:
FOR UPDATE— другая транзакция не сможет обновить или удалить заблокированные строки, она будет ждать.FOR NO KEY UPDATE— это блокировка, которую PostgreSQL ставит при обычномUPDATE. По сути, она защищает строку от параллельных изменений, но не блокирует операции, связанные только с проверкой внешних ключей.FOR SHAREиFOR KEY SHARE— используются, когда строку читают, но при этом нужно гарантировать, что её не удалят и не изменят значения, от которых зависят другие данные (например, при работе внешних ключей).
Advisory locks
Есть отдельный тип блокировок, который вообще не привязан к таблицам — advisory locks. Это блокировки, которые приложение устанавливает вручную.
SELECT pg_advisory_lock(1);
Это удобно, когда нужно синхронизировать не строку в базе, а что-то на уровне логики приложения: обработку заказа, задачу в очереди, и т.п.
Но здесь нет никакой защиты от ошибок — если в коде допустить несогласованность, PostgreSQL это не исправит.
Внутренние блокировки
Помимо пользовательских блокировок, в PostgreSQL есть внутренние — для работы с буферами, индексами и памятью. Они живут очень недолго и управляются самой системой. Обычно их стоит затрагивать, только при поиске слабых мест в производительности.
С блокировками в PostgreSQL обычно начинают разбираться, когда запросы начинают ждать друг друга и время ответа резко растёт. Но на практике достаточно понимать несколько вещей: какие операции ставят блокировки, на каком уровне они работают и в каких случаях начинается конфликт.
Дальше уже проще — большинство проблем решается либо более точечными блокировками, либо переписыванием запроса так, чтобы он был атомарным.