#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
.