Последовательные операторы SQL с состоянием - PullRequest
0 голосов
/ 22 марта 2009

Я пишу простую программу обмена сообщениями, в которой есть таблица сообщений, которая может быть востребована пользователями, и что пользователь сделал с ними что-то. Не предопределено, какой пользователь будет запрашивать данное сообщение, и поэтому я хочу, чтобы запрос выбрал сначала из всех доступных сообщений, которые у меня есть, а затем один, чтобы пометить это сообщение как полученное, что у меня также есть. Проблема в том, что я не хочу, чтобы два пользователя, использующие его одновременно, запрашивали одно и то же сообщение, и поэтому я хочу выполнить два оператора последовательно, не возвращаясь к программе, чтобы выяснить, что будет дальше в между заявлениями. Я полагаю, что могу выполнить два последовательных оператора, разделив их точкой с запятой, но я хочу использовать данные, возвращенные в первом запросе, как часть второго. Переменные были бы идеальны, но, насколько я знаю, они не существуют в SQL. Есть ли способ сохранить состояние между запросами?

Ответы [ 5 ]

2 голосов
/ 22 марта 2009

Для этого нужны BEGIN TRAN и COMMIT TRAN. Поместите заявления, которые вы хотите защитить, в транзакции.

1 голос
/ 22 марта 2009

Есть ли способ сохранить состояние между запросами?

Нет. SQL не процедурный язык. Вы можете переписать два ваших запроса как один запрос (это не всегда возможно, часто не стоит, даже если это возможно) или склеить их вместе с процедурным языком. Многие SQL-серверы предоставляют для этого встроенный язык («хранимые процедуры»), или вы можете сделать это в своем приложении.

Проблема в том, что я не хочу, чтобы два пользователя, использующие его одновременно, требовали одно и то же сообщение

Используйте замки. Я не знаю, какой SQL-сервер вы используете, но использование SELECT ... FOR UPDATE звучит так, как будто это то, что вы хотите, если он доступен.

0 голосов
/ 22 марта 2009

Сам по себе 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 , чтобы увидеть, действительно ли строки были обновлены.

0 голосов
/ 22 марта 2009

Вы можете использовать временную таблицу, возможно.

0 голосов
/ 22 марта 2009

Транзакции - хороший способ, как говорит Ле Дорфье, но есть предупреждения:

Сначала вы можете выполнить обновление, т. Е. Пометить сообщение идентификатором пользователя или аналогичным. Вы не упоминаете, какой SQL-аромат вы используете, но в MySQL я думаю, что это будет выглядеть примерно так:

UPDATE message
SET    user_id = ...
WHERE  user_id = 0   -- Ensures no two users gets the same message
LIMIT 1

В ms sql это будет что-то вроде:

WITH q AS (
  SELECT TOP 1
  FROM message m
  WHERE user_id = 0
) 
UPDATE q
SET    user_id = 1

/ B

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...