Сам по себе SQL не имеет переменных, но (почти?) Все расширения SQL RDBMS имеют. Но я не совсем уверен, как одно это решит вашу проблему.
Как уже упоминалось, транзакция поможет, эффективно сгруппировав два несвязанных утверждения. Однако , уровень транзакции по умолчанию не будет работать . (Больше всего?) Уровень транзакции RDBMS-сервера по умолчанию READ COMMITTED. Это не мешает пользователю 2 читать ту же строку, что и пользователь 1. Для этого вам необходимо использовать REPEATABLE READ или SERIALIZABLE.
Это классическая проблема параллелизма. Как правило, два способа обработки - это пессимистическая блокировка или оптимистическая проверка. Транзакция REPEATABLE READ была бы пессимистичной (нести расходы на блокировку независимо от того, была ли она необходима), а проверка @@ ROWCOUNT - оптимистичной (предполагая, что она будет работать, но при выполнении @@ ROWCOUNT = 0) что-то разумное.
Обычно мы используем оптимистический режим (блокировка обходится дорого) и либо используем временную метку, либо комбинацию прочитанных полей, чтобы гарантировать, что мы изменяем данные, которые мы думали. Поэтому я предлагаю включить поле rowversion или timestamp и передать его обратно в свой оператор UPDATE. Затем проверьте @@ ROWCOUNT, чтобы увидеть, обновили ли вы какие-либо записи. Если вы этого не сделали, вернитесь и выберите другое сообщение. В псевдокоде:
int messageId, byte[] rowVersion = DB.Select(
"SELECT TOP 1
MessageId, RowVersion
FROM Messages
WHERE
User IS NULL";
int rowsAffected = DB.Update(
"UPDATE Messages SET
User = @myUserId
WHERE
MessageId = @messageId
AND RowVersion = @rowVersion",
myUserId, messageId, rowVersion
);
if (rowsAffected = 0)
throw new ConcurrencyException("The message was taken by someone else");
В зависимости от ваших конкретных утверждений вы можете избежать повторения предложения WHERE «UserId IS NULL» в своем выражении UPDATE. Это похоже на решение Бримштедта - но вы все равно должны проверить @@ ROWCOUNT , чтобы увидеть, действительно ли строки были обновлены.