Как предотвратить состояние состязания в базе данных MySQL, когда два соединения хотят обновить ту же запись?
Например, соединение 1 хочет увеличить счетчик "попыток". И второе соединение хочет сделать то же. Оба соединения SELECT
количество "попыток", увеличьте значение и обоих UPDATE
"попытки" с увеличенным значением. Внезапно "попытки" только "tries+1" вместо того, чтобы быть "tries+2", потому что оба соединения получили те же "попытки" и увеличили его одним.
Как решить эту проблему?
Вот 3 разных подхода:
update table set tries=tries+1 where condition=value;
, которое будет выполняться атомарно.
Если вам действительно нужно сначала выбрать значение и обновить его в вашем приложении, вам, вероятно, придется использовать транзакции. Это означает, что вам придется использовать InnoDB, а не таблицы MyISAM. Ваш запрос будет примерно таким:
BEGIN; //or any method in the API you use that starts a transaction
select tries from table where condition=value for update;
.. do application logic to add to `tries`
update table set tries=newvalue where condition=value;
END;
если транзакция завершится неудачно, вам может потребоваться повторить ее вручную.
Распространенным подходом является введение столбца версии в вашу таблицу. Ваши запросы будут делать что-то вроде:
select tries,version from table where condition=value;
.. do application logic, and remember the old version value.
update table set tries=newvalue,version=version + 1 where condition=value and version=oldversion;
Если это обновление завершится неудачно / вернет 0 затронутых строк, это значит, что кто-то еще обновил таблицу за это время. Вы должны начать все сначала - то есть выбрать новые значения, выполнить логику приложения и повторить попытку обновления.
Используйте одну инструкцию вместо двух. Один оператор UPDATE
, который выполняет и чтение, и запись, будет атомарным и не будет конфликтовать с другим одновременным обновлением.
UPDATE table SET tries = tries + 1 WHERE ...
Или вы можете использовать транзакции, чтобы сделать две операции атомарными.
BEGIN
SELECT ...
UPDATE ...
COMMIT
Или, что более примитивно, заблокируйте таблицу на время чтения / записи.
LOCK TABLES table WRITE
SELECT ...
UPDATE ...
UNLOCK TABLES