Почему UPDATE блокирует строки, а не таблицу

Если задать вопрос: "Что блокируется при UPDATE строки или таблица?", думаю что большинство разработчиков не задумываясь ответит — строки. Но не всё так однозначно, как хотелось бы. Ниже мы разберёмся почему PostgreSQL предпочитает блокировать строки и в каких случаях обновление может заблокировать почти всю таблицу.

Что такое блокировки

Для полного рассказа о блокировках понадобится отдельная статья. Здесь я вкратце поясню про них для тех кто забыл, или не знал

Блокировки — это инструмент базы данных для сохранения целостности данных. Чтобы разные транзакции не мешали друг другу работать с данными, СУБД может заблокировать или строки или всю таблицу целиком для одной из них, и тогда остальные будут ждать пока блокировка снимется

Почему UPDATE выбирает строки, а не всю таблицу

В большинстве случаев при UPDATE указываются условия по которым нужно обновить данные (WHERE). PostgreSQL смотрит на условие, находит нужные строки и блокирует только их. Остальные данные остаются доступны для других запросов.

Примеры:

-- Возьмём таблицу table_name с первичным ключом id:
UPDATE table_name
    SET name = 'new_name'
WHERE id = 777;
-- PostgreSQL находит по ключу строку с id = 777 -> блокирует её -> создаёт новую версию строки
-- с обновлённым значением -> снимает блокировку (блокировка держится до конца транзакции, это важно учитывать если в транзакции есть другие запросы)

-- Возьмём таблицу table_name_2 с индексом на столбцах time_create, active
UPDATE table_name_2
    SET active = 0
WHERE time_create <= '2026-01-01'
  AND active = 1;
-- Если планировщик использует индекс (time_create, active), база находит подходящие строки -> блокирует их -> обновляет
-- -> снимает блокировки (блокировки держатся до конца транзакции)

Важно понимать, что в PostgreSQL обновление работает через MVCC: при UPDATE не изменяется строка, а создаётся её новая версия. Блокировка нужна только для того, чтобы две транзакции одновременно не изменили одну и ту же строку.

Почему при UPDATE может казаться, что заблокирована вся таблица

Пример блокировки без индекса:

-- Возьмём таблицу table_name с первичным ключом id:
UPDATE table_name
    SET active = 0
WHERE name ~* 'search_name';
-- Для того чтобы найти строки удовлетворяющие условию PostgreSQL нужно просканировать всю таблицу:
-- База читает строки одну за другой -> как только строка подходит под условие, блокирует её -> обновляет
-- Остальные строки при этом не блокируются

На практике такие ситуации чаще всего связаны не с типом блокировки, а с количеством затронутых строк. Вот основные причины по которым при UPDATE может создаваться ощущение, что заблокирована вся таблица:

  1. Отсутствие индекса по полю(ям) в условии — в этом случае PostgreSQL приходится сканировать всю таблицу, при последовательном сканировании запрос может “упираться” в уже заблокированные строки и ждать, из-за чего создаётся эффект полной блокировки таблицы
  2. Слишком обширное условие WHERE — если под него попадает большая часть таблицы (80–90% строк), будет заблокировано почти столько же строк. В итоге для других транзакций это может выглядеть как «заблокирована вся таблица», хотя технически блокировки остаются построчными

Полезные советы:

  • Лучше добавлять индексы на поля которые часто используются в условиях UPDATE. Это ускорит поиск и позволит уменьшить область блокировки
  • Если есть возможность, то стоит разбивать массовые UPDATE на несколько партий. За счёт этого можно снизить нагрузку и время блокировки
  • Проверяйте новые запросы с помощью EXPLAIN (а лучше EXPLAIN ANALYZE, но только на тестовой базе, т.к. ANALYZE выполнит запрос) — так можно узнать сколько строк затронет UPDATE и использует ли он индексы