Postgres: SELECT FOR UPDATE не видит новые строки после снятия блокировки

Пытаясь поддерживать базу данных PostgreSQL в моем приложении, я обнаружил странное поведение.

Подготовка:

CREATE TABLE test(id INTEGER, flag BOOLEAN);
INSERT INTO test(id, flag) VALUES (1, true);

Предположим, что две одновременные транзакции (Autocommit = false, READ_COMMITTED) TX1 и TX2:

TX1:

UPDATE test SET flag = FALSE WHERE id = 1;
INSERT INTO test(id, flag) VALUES (2, TRUE);
-- (wait, no COMMIT yet)

TX2:

SELECT id FROM test WHERE flag=true FOR UPDATE;
-- waits for TX1 to release lock

Теперь, если я COMMIT в TX1, SELECT в TX2 вернет пустой курсор.

Мне это странно, потому что один и тот же эксперимент в Oracle и MariaDB приводит к выбору вновь созданной строки (id = 2).

Я не смог найти ничего об этом в документации PG. Я что-то упускаю? Есть ли способ заставить PG-сервер «обновить» видимость оператора после получения блокировки?

PS: PostgreSQL версии 11.1


person Ivan Fedenkov    schedule 08.02.2019    source источник


Ответы (1)


TX2 сканирует таблицу и пытается заблокировать результаты.

Сканирование видит моментальный снимок базы данных с самого начала запроса, поэтому он не может видеть какие-либо строки, которые были вставлены (или допущены каким-либо другим способом) параллельными модификациями, которые начались после, когда был сделан этот моментальный снимок .

Вот почему вы не видите строку с id 2.

Для id 1 это также верно, поэтому сканирование находит эту строку. Но запрос должен дождаться снятия блокировки. Когда это, наконец, происходит, он выбирает последнюю зафиксированную версию строки и снова выполняет проверку, так что эта строка также исключается.

Эта перепроверка EvalPlanQual (если использовать жаргон PostgreSQL) выполняется только для строк, которые были обнаружены во время сканирования, но были заблокированы. Вторая строка даже не обнаруживается при сканировании, поэтому там такой обработки не происходит.

Признаюсь, это немного странно. Но это не ошибка, это просто способ работы PostgreSQL.

Если вы хотите избежать подобных аномалий, используйте уровень изоляции REPEATABLE READ. В этом случае вы получите ошибку сериализации и сможете повторить транзакцию, избегая таким образом несоответствий.

person Laurenz Albe    schedule 15.02.2019