Сегодня я расскажу про одну интересную особенность PostgreSQL, с которой сталкивался лично: внезапные подвисания при UPDATE большого количества строк. Причём CPU почти не загружен, а запрос как будто "висит".
Проблема часто кроется в отсутствии индекса на колонку фильтра в WHERE. Пример:
Если на created_at нет индекса, то PostgreSQL делает sequential scan всей таблицы. А теперь внимание: если в таблице много "мертвых" строк, которых ещё не убрал autovacuum, то PostgreSQL должен:
1. Прочитать кучу ненужных версий строк (MVCC).
2. Проверять видимость каждой строки.
3. Иногда ещё и ждать завершения других транзакций, держащих старые снапшоты.
Что делать:
- Проверить наличие индекса на колонку фильтра:
- Проверить состояние autovacuum:
- Можно вручную запустить:
Лайфхак: если UPDATE всё равно медленный, попробуй его разбить на батчи по 10 000 строк. Это снизит нагрузку и ускорит выполнение.
Проблема часто кроется в отсутствии индекса на колонку фильтра в WHERE. Пример:
SQL:
UPDATE orders SET status = 'archived' WHERE created_at < '2022-01-01';
Если на created_at нет индекса, то PostgreSQL делает sequential scan всей таблицы. А теперь внимание: если в таблице много "мертвых" строк, которых ещё не убрал autovacuum, то PostgreSQL должен:
1. Прочитать кучу ненужных версий строк (MVCC).
2. Проверять видимость каждой строки.
3. Иногда ещё и ждать завершения других транзакций, держащих старые снапшоты.
Что делать:
- Проверить наличие индекса на колонку фильтра:
SQL:
CREATE INDEX idx_orders_created_at ON orders(created_at);
- Проверить состояние autovacuum:
SQL:
SELECT relname, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;
- Можно вручную запустить:
SQL:
VACUUM ANALYZE orders;
Лайфхак: если UPDATE всё равно медленный, попробуй его разбить на батчи по 10 000 строк. Это снизит нагрузку и ускорит выполнение.