2

Я столкнулся с проблемами состояния транзакций в PostgreSQL (возможно, не только psql). Я пытаюсь добиться такой простой задачи с использованием нескольких потоков:SELECT FOR UPDATE ошибочный результат

BEGIN; 
SELECT * FROM t WHERE id = 1; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

Однако потоки сталкивающихся внутри этих операций, так что несколько вставок, выполненных (ключевой ошибкой первичного соударения). Таким образом, я пытался использовать SELECT FOR UPDATE заявление:

BEGIN; 
SELECT * FROM t WHERE id = 1 FOR UPDATE; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

Сделки правильно блокированием FOR UPDATE заявление ждет других потоков фиксации.

Однако после того, как «семафор вверх» (проснувшись на это заявление после того, как другой поток транзакции совершил) пустой результирующий набор возвращается из СУБД, хотя данные правильно доступны в таблице (от INSERT заявления от более быстрого потока):

BEGIN; 
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- blocking ... then return 0 records WRONG 
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- second try ... returns 1 record CORRECT 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

Как видно выше, второй (дублированный) оператор выбора ведет себя правильно. Зачем?

ответ

0

Причина заключается в том, что снимок блокированного оператора старше транзакции, которая вставляла новую строку, поэтому она не может видеть ее после удаления блокировки.

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

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