Как атомизировать заявку на строку или ресурс с помощью UPDATE в mysql
У меня есть таблица ресурсов (скажем, автомобили), на которые я хочу претендовать атомарно. Затем я хочу получить информацию о том, какой ресурс я только что заявлял.
Если у одного пользователя есть ограничение на один ресурс, я могу сделать следующий трюк:
UPDATE cars SET user = 'bob' WHERE user IS NULL LIMIT 1
SELECT * FROM cars WHERE user = 'bob'
Таким образом, я утверждаю ресурс атомарно, и тогда я могу видеть, какую строку я только что заявил.
Это не работает, когда "bob" может требовать несколько автомобилей. Я понимаю, что могу получить список автомобилей, уже заявленных bob, попросить еще один, а затем SELECT
снова, чтобы увидеть, что изменилось, но это кажется хакерским.
Что мне интересно, есть ли способ увидеть, какие строки я только что обновил с помощью моего последнего UPDATE?
В противном случае, есть ли какой-нибудь другой трюк, чтобы атомически требовать строку? Я действительно хочу избежать использования уровня изоляции SERIALIZABLE
. Если я сделаю что-то вроде этого:
1 SELECT id FROM cars WHERE user IS NULL
2 <here, my PHP or whatever picks a car id>
3 UPDATE cars SET user = 'bob' WHERE id = <the one i picked>
было бы REPEATABLE READ
достаточно здесь? Другими словами, могу ли я быть уверенным в том, что некоторые другие транзакции не будут требовать того, что моя программа выбрала во время шага 2?
Ответы
Ответ 1
UPDATE cars SET user = 'bob' WHERE id = 123 И пользователь IS NULL;
Запрос на обновление возвращает количество измененных строк. Если он ничего не обновил, вы знаете, что автомобиль уже был заявлен кем-то другим.
В качестве альтернативы вы можете использовать SELECT... FOR UPDATE.
Ответ 2
Одна вещь, которую вы можете использовать, - SELECT FOR UPDATE. Это позволит вам сделать свой выбор, узнать, что вы выбрали, а затем обновить эти значения. Блокировка освобождается при завершении транзакции.
<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "test");
mysqli_autocommit($link, FALSE);
$result = mysqli_query($link, "SELECT id FROM cars WHERE user IS NULL");
// do something with the results
mysqli_query($link, "UPDATE cars SET user = 'bob' WHERE id = <the one that was picked>");
mysqli_commit($link);
mysqli_close($link);
?>
Ответ 3
update cars
set @id = id, user= 'bob'
where user is null
гарантированно будет атомарным, а @id расскажет вам, какая последняя обновленная строка.
Ответ 4
Я не уверен, почему в этих ответах так много дезинформации, но ответ прост (даже если есть расширенная тема SQL). Это то, что "блокировка" для почти любых РСУБД. Точный синтаксис зависит от поставщика и версии, а некоторые предлагают синтаксис, который пытается скрыть блокировку от пользователя (обычно, когда и выбор, и обновление находятся в одном запросе).
Для MySQL вы должны сначала использовать SELECT ... FROM ... FOR UPDATE;
, который сообщает базе данных об установке исключительной блокировки для каждой возвращаемой записи.
Важно не блокировать больше строк, чем вам абсолютно необходимо! Сделайте запрос "ВЫБЕРИТЕ ДЛЯ ОБНОВЛЕНИЯ" столь же гранулированным, насколько это возможно, с либеральным использованием предложений "WHERE" и "LIMIT".
Впоследствии, когда одно и то же соединение с базой данных вызывает UPDATE ...
в тех же строках, которые были ранее заблокированы, эта блокировка освобождается, а другие могут снова получить доступ к этой строке.
Итак, скажем, у вас есть очередь заданий с полем "STATUS", который используется для задания состояния выполнения каждого задания. 0 для очереди, 1 находится в процессе выполнения, а 2 - для завершения, 3 - для отказа и т.д.
Каждый бегун может атомарно получить задание для запуска (так, чтобы ни один из двух бегунов не попытался выполнить одно и то же задание), выпустив следующее:
SELECT ID, * FROM JOBS WHERE STATUS = 0 LIMIT 1 FOR UPDATE;
затем
UPDATE JOBS SET STATUS = 1 WHERE JOBS.ID = X;
то он может запустить задание и обновить базу данных по завершении:
UPDATE JOBS SET STATUS = [2|3] WHERE JOBS.ID = X;
Внимание!
Из документации MySQL:
Все блокировки, установленные с помощью запросов LOCK IN SHARE MODE и FOR UPATE, освобождаются при совершении или откате транзакции.
SELECT FOR UPDATE
не блокирует записи, если включена автоматическая учетная запись. Либо отключите autocommit, либо (желательно), используя START TRANSACTION; SELECT ... FROM ... FOR UPDATE; UPDATE ...; END TRANSACTION;