как атомарно требовать строку или ресурс, используя UPDATE в MySQL - PullRequest
13 голосов
/ 17 марта 2010

У меня есть таблица ресурсов (скажем, машины), которую я хочу получить атомарно. Затем я хочу получить информацию о том, на какой ресурс я только что претендовал.

Если существует ограничение в один ресурс на одного пользователя, я могу сделать следующий трюк:

UPDATE cars SET user = 'bob' WHERE user IS NULL LIMIT 1
SELECT * FROM cars WHERE user = 'bob'

Таким образом, я запрашиваю ресурс атомарно, а затем вижу, какую строку я только что запросил.

Это не работает, когда «боб» может претендовать на несколько автомобилей. Я понимаю, что могу получить список автомобилей, уже заявленных Бобом, запросить другой, а затем SELECT еще раз, чтобы увидеть, что изменилось, но это выглядит хакерским

Что меня интересует, так это какой-нибудь способ узнать, какие строки я только что обновил с моим последним обновлением?

Если это не удастся, есть ли какой-нибудь другой трюк для атомарного получения строки? Я действительно хочу избежать использования 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?

Ответы [ 4 ]

10 голосов
/ 17 марта 2010

ОБНОВЛЕНИЕ автомобилей SET user = 'bob' WHIDE id = 123 И пользователь NULL;

Запрос на обновление возвращает количество измененных строк. Если он не обновлен, вы знаете, что автомобиль уже был заявлен кем-то другим.

Кроме того, вы можете использовать SELECT ... ДЛЯ ОБНОВЛЕНИЯ.

2 голосов
/ 12 августа 2017

Я не уверен, почему в этих ответах так много дезинформации, но ответ прост (даже если речь идет о продвинутой теме SQL). Вот для чего нужна «блокировка» практически в любой СУБД. Точный синтаксис зависит от поставщика и версии, а некоторые предлагают синтаксис, который пытается скрыть блокировку от пользователя (как правило, когда выбор и обновление находятся в одном запросе).

Для MySQL вы сначала должны использовать SELECT ... FROM ... FOR UPDATE;, который говорит базе данных устанавливать эксклюзивную блокировку для каждой возвращаемой записи.

Важно не блокируйте больше строк, чем вам абсолютно необходимо! Сделайте запрос «SELECT FOR UPDATE» настолько гранулированным, насколько это возможно, с либеральным использованием предложений «WHERE» и «LIMIT».

Впоследствии, когда то же соединение с базой данных выдает UPDATE ... для тех же строк, которые были ранее заблокированы, эта блокировка снимается, и другие могут снова получить доступ к этой строке.

Допустим, у вас есть очередь заданий с полем «СОСТОЯНИЕ», которое используется для установки состояния выполнения каждого задания. 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 UPDATE, снимаются при фиксации или откате транзакции.

SELECT FOR UPDATE не блокирует записи, если включен автокоммит. Отключите автокоммит или (желательно), используя START TRANSACTION; SELECT ... FROM ... FOR UPDATE; UPDATE ...; END TRANSACTION;

2 голосов
/ 17 марта 2010
update cars  
set @id = id, user= 'bob' 
where user is null

гарантированно будет атомарным, а @id сообщит вам, какую последнюю строку вы обновили.

1 голос
/ 17 марта 2010

Одна вещь, которую вы можете использовать, это ВЫБРАТЬ ОБНОВЛЕНИЕ . Это позволит вам сделать выбор, узнать, что вы выбрали, а затем обновить эти значения. Блокировка снимается после завершения транзакции.

<?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);
?>
...