Катя Кондратьева
Фотография
нажмите — покажем
нажмите — покажем
Фотография
нажмите — покажем
нажмите — покажем
Фотография
нажмите — покажем
нажмите — покажем
Почему PostgreSQL откатывает изменения разных строк
Вспомнила вопрос с недавнего собеса и решила разобрать в канале.
Две транзакции меняют разные счётчики, но одна из них всё равно может завершиться ошибкой. Причина в том, как база отслеживает захват объектов.
Пример: таблица counters(id PRIMARY KEY, value), без триггеров и связей с другими таблицами. Обе транзакции работают в SERIALIZABLE. Сначала выполняются оба SELECT, затем оба UPDATE и попытки COMMIT:
-- Транзакция T1
SELECT value FROM counters WHERE id = 1;
UPDATE counters SET value = value + 1 WHERE id = 1;
-- Транзакция T2
SELECT value FROM counters WHERE id = 2;
UPDATE counters SET value = value + 1 WHERE id = 2;
SERIALIZABLE обещает результат успешных транзакций как будто транзакции выполняются последовательно.
Для проверки база запоминает, кто какую область читал. Эти отметки - SIReadLock - не блокируют запись. Они помогают заметить, что чужое изменение затронуло прочитанные данные.
Пример: T1 прочитала старое значение, а T2 его изменила. Значит, в последовательном выполнении T1 должна быть раньше T2. Иначе она увидела бы новое значение. Так появляется зависимость T1 → T2.
При Seq Scan - последовательном чтении таблицы - отметка чтения ставится на всю таблицу, хотя запрос меняет одну строку.
Поэтому база учитывает две зависимости:
- T1 меняет строку 1 → задевает отметку T2 → T2 должна быть раньше T1.
- T2 меняет строку 2 → задевает отметку T1 → T1 должна быть раньше T2.
Требования противоречат друг другу. PostgreSQL отклоняет одну транзакцию с ошибкой serialization_failure, код 40001.
Это ложный конфликт: счётчики можно изменить в любом порядке, но широкая отметка связала две независимые операции.
Чтение через индекс может сузить отметку до строки/страницы и тем самым уменьшить число конфликтов. Поэтому при разборе таких откатов полезно проверить план выполнения запроса. Но полностью исключить конфликты нельзя: приложение должно уметь при ошибке 40001 повторить всю транзакцию, включая чтения.
классная статья про блокировки в постгрес
#production_case
64 · 2.3K ·