Потерянное обновление (LOST Update) PostgreSQL
Аномалия потерянного обновления (lost update) возникает тогда, когда две транзакции читают одну и ту же строку таблицы, затем одна из них обновляет эту строку, после чего вторая обновляет эту же строку, не учитывая изменений, сделанных первой транзакцией. Потерянное обновление не допускается стандартом ни на одном уровне изоляции.
В этом разделе дается подробное описание того, как PostgreSQL предотвращает потерю обновления.
Параллельные команды UPDATE
При выполнении команды UPDATE вызывается функция ExecUpdate. Псевдокод функции ExecUpdate показан ниже:
(1) FOR each row that will be updated by this UPDATE command
(2) WHILE true
/*
* The First Block
*/
(3) IF the target row is 'being updated' THEN
(4) WAIT for the termination of the transaction that updated the target row
(5) IF (the status of the terminated transaction is COMMITTED)
AND (the isolation level of this transaction is REPEATABLE READ or SERIALIZABLE) THEN
(6) ABORT this transaction /* First-Updater-Win */
ELSE
(7) GOTO step (2)
END IF
/*
* The Second Block
*/
(8) ELSE IF the target row has been updated by another concurrent transaction THEN
(9) IF (the isolation level of this transaction is READ COMMITTED THEN
(10) UPDATE the target row
ELSE
(11) ABORT this transaction /* First-Updater-Win */
END IF
/*
* The Third Block
*/
ELSE /* The target row is not yet modified */
/* or has been updated by a terminated transaction. */
(12) UPDATE the target row
END IF
END WHILE
END FOR
- (1) Получение каждого ряда, который будет обновлен при помощи команды UPDATE;
- (2) Повторение этого процесса до тех пор, пока не будет обновлена целевая строка (или эта транзакция будет прервана);
- (3) Если целевая строка обновляется, перейдите к шагу (3); в противном случае перейдите к шагу (8);
- (4) Дождитесь завершения транзакции, которая обновила целевую строку, так как PostgreSQL использует схему " first-updater-win";
- (5) Если статус транзакции, которая обновила целевую строку, - COMMITTED и уровень изоляции этой транзакции - REPEATABLE READ (или SERIALIZABLE), перейдите к шагу (6); в противном случае перейдите к шагу (7);
- (6) Abort this transaction to prevent Lost Updates.
- (7) Перейдите к шагу (2) и попытайтесь обновить целевой ряд в следующем цикле;
- (8) Если целевая строка была обновлена другой параллельной транзакцией, перейдите к шагу (9); в противном случае перейдите к шагу (12);
- (9) Если уровень изоляции данной транзакции - READ COMMITTED, перейдите к шагу (10); в противном случае перейдите к шагу (11);
- (10) Обновите целевую строку и перейдите к шагу (1);
- (11) Прервите данную операцию для того, чтобы предотвратить потерю обновления;
- (12) Обновите целевую строку и перейдите к шагу (1), поскольку целевая строка еще не изменена в следствие чего может произойти потеря обновлений.
Эта функция выполняет операции обновления для каждой целевой строки. Для обновления каждой строки у нее есть цикл while, внутри которого происходит разделение на три блока в соответствии с условиями, показанными на рис. 58.
- [1] Целевая строка обновляется (рис. 58[1])
'Обновляется' означает, что строка обновляется другой параллельной транзакцией, и эта транзакция еще не завершилась.
В данном случае текущая транзакция должна дождаться завершения транзакции, обновившей целевую строку, поскольку в SI PostgreSQL используется схема " first-updater-win".
Например, предположим, что транзакции Tx_A и Tx_B выполняются параллельно, и Tx_B пытается обновить строку; однако Tx_A уже обновила ее и все еще находится в процессе выполнения. В этом случае Tx_B ожидает завершения Tx_A.
После фиксации транзакции, обновившей целевую строку, операция обновления текущей транзакции продолжится.
Если уровень изоляции текущей транзакции - READ COMMITTED, целевая строка будет обновлена; в противном случае (REPEATABLE READ или SERIALIZABLE) текущая транзакция будет немедленно прервана, что позволит избежать потери обновления.
-
[2] Целевая строка была обновлена параллельной транзакцией (рис. 58[2])
Текущая транзакция пытается обновить целевой кортеж, однако другая транзакция уже обновила целевую строку и была зафиксирована.
В данном случае, если уровень изоляции текущей транзакция - READ COMMITTED, целевая строка будет обновлена; в противном случае текущая транзакция немедленно прервана, что позволит избежать потери обновления.
- [3] Конфликт отсутствует (рис. 58[3])
Если конфликта нет, текущая транзакция может обновить целевую строку.
Примечание: First-updater-win / First-commiter-win
Как уже говорилось ранее, управление параллелизмом в PostgreSQL основано на SI и использовании схемы first-updater-win, позволяющей избежать потери обновления. В целях недопущения аномалии сериализации SSI в PostgreSQL использует схему " first-committer-win ", о чем Вы узнаете из последующих подразделов.
Примеры
Ниже представлены три примера. Первый и второй примеры отражают процесс, когда целевая строка находится в процессе обновления. Третий пример иллюстрирует ситуацию, когда целевая строка уже была обновлена.
Пример 1
Транзакции Tx_A и Tx_B обновляют одну и ту же строку в одной и той же таблице, их уровень изоляции - READ COMMITTED.
testdb=# -- Tx_A testdb=# START TRANSACTION testdb-# ISOLATION LEVEL READ COMMITTED; START TRANSACTION testdb=# UPDATE tbl SET name = 'Hyde'; UPDATE 1 testdb=# COMMIT; COMMIT testdb=# testdb=# -- Tx_B testdb=# START TRANSACTION testdb-# ISOLATION LEVEL READ COMMITTED; START TRANSACTION testdb=# UPDATE tbl SET name = 'Utterson'; (this transaction is being blocked) UPDATE 1
Tx_B выполняется следующим образом:
- После выполнения команды UPDATE Tx_B должна дождаться завершения Tx_A, так как целевой кортеж обновляется Tx_A (шаг (4) в ExecUpdate);
- После фиксации Tx_A Tx_B пытается обновить целевую строку (шаг (7) в ExecUpdate);
- Во втором цикле ExecUpdate целевая строка снова обновляется Tx_B (шаги (2),(8),(9),(10) в ExecUpdate).
Пример 2
Tx_A и Tx_B обновляют одну и ту же строку в одной и той же таблице, их уровни изоляции - READ COMMITTED и REPEATABLE READ соответственно.
testdb=# -- Tx_A testdb=# START TRANSACTION testdb-# ISOLATION LEVEL READ COMMITTED; START TRANSACTION testdb=# UPDATE tbl SET name = 'Hyde'; UPDATE 1 testdb=# COMMIT; COMMIT testdb=# testdb=# -- Tx_B testdb=# START TRANSACTION testdb-# ISOLATION LEVEL REPEATABLE READ; START TRANSACTION testdb=# UPDATE tbl SET name = 'Utterson'; (this transaction is being blocked) ERROR:couldn't serialize access due to concurrent update
Tx_B выполняется следующим образом:
- После выполнения команды UPDATE Tx_B должна дождаться выполнения Tx_A (шаг (4) в ExecUpdate);
-
После фиксации Tx_A, Tx_B прерывается для разрешения конфликта, поскольку целевая строка была обновлена, а уровень изоляции этой транзакции - REPEATABLE READ (шаги (5) и (6) в ExecUpdate).
Пример 3
Tx_B (REPEATABLE READ) пытается обновить целевую строку, которая уже была обновлена зафиксированной Tx_A. В этом случае Tx_B немедленно прерывается (шаги (2), (8), (9) и (11) в ExecUpdate).
testdb=# -- Tx_A testdb=# START TRANSACTION testdb-# ISOLATION LEVEL READ COMMITTED; START TRANSACTION testdb=# UPDATE tbl SET name = 'Hyde'; UPDATE 1 testdb=# COMMIT; COMMIT testdb=# testdb=# testdb=# -- Tx_B testdb=# START TRANSACTION testdb-# ISOLATION LEVEL REPEATABLE READ; START TRANSACTION testdb=# SELECT * FROM tbl; name -------- Jekyll (1 row) testdb=# UPDATE tbl SET name = 'Utterson'; ERROR:couldn't serialize access due to concurrent update




