Не удается найти решение проблемы с потерянным обновлением MySQL

#mysql #sql #concurrency

Вопрос:

Я пытаюсь исправить проблему с потерянным обновлением в MySQL, когда два сеанса не обновляются друг с другом. В основном, вопрос заключается в следующем:

 A owes B $30. Later, A owes B another $20. In the end A needs to owe both $30 and $20, where A eventually has 50 and B has 150.
 

У меня открыты две терминальные сессии, но я не уверен, как я могу решить проблему параллелизма. Я обыскал все, чтобы найти решение, но мне не повезло, как числа A и B могут дать один и тот же результат за два сеанса.

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

Сессия А:

 UPDATE accounts SET balance = 100;
SELECT * FROM accounts;

START TRANSACTION;

SELECT balance INTO @user1_balance from accounts where id = 1;
SELECT balance INTO @user2_balance from accounts where id = 2;

SELECT @user1_balance, @user2_balance;
 

Сессия В:

 START TRANSACTION;

SELECT balance INTO @user1_balance from accounts where id = 1;
SELECT balance INTO @user2_balance from accounts where id = 2;

SELECT @user1_balance, @user2_balance;
 

Сессия А:

 UPDATE accounts 
SET balance = @user1_balance - 30 
WHERE id = 1;

UPDATE accounts 
SET balance = @user2_balance   30 
WHERE id = 2;

COMMIT;

SELECT * FROM accounts;
 

Сессия В:

 SELECT * FROM accounts;
 

Сессия В:

 UPDATE accounts 
SET balance = @user1_balance - 20 
WHERE id = 1;

UPDATE accounts 
SET balance = @user2_balance   20 
WHERE id = 2;

COMMIT;

SELECT * FROM accounts;
 

Если я запущу эти

Ответ №1:

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

Этого не произошло бы в производственной среде, потому что нет необходимости хранить баланс на потом.

Если вы применяете свои обновления непосредственно в рамках транзакции, все должно быть согласовано:

Терминал А:

 START TRANSACTION;
UPDATE accounts set balance = balance -30 where id = 1;
UPDATE accounts set balance = balance  30 where id = 2;
 

Терминал В:

 START TRANSACTION;
UPDATE accounts set balance = balance -20 where id = 1;
-- This update is blocked by the outstanding COMMIT on terminal A. Terminal B waits
 

Терминал А:

 COMMIT;
 

Терминал В:

 -- The COMMIT on Terminal A unlocks the rows, so we can complete our update.
UPDATE accounts set balance = balance  20 where id = 2;
COMMIT;
 

Теперь оба терминала показывают один и тот же результат:

 select * from accounts;
 ---- --------- 
| id | balance |
 ---- --------- 
|  1 |   50.00 |
|  2 |  150.00 |
 ---- --------- 

 

Ответ №2:

Вам не нужно запускать a SELECT , чтобы назначить балансы пользователей. Это можно сделать в UPDATE

 UPDATE accounts 
SET balance = balance  - 20 
WHERE id = 1;
 

Обратите внимание на balance = balance - 20 .