2009-03-21 3 views
0

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

ответ

0

Сделка хороший путь, как Ле dorfier говорит, но есть alernatives:

Вы могли бы сделать обновление первым, т.е. мечение сообщения с идентификатором пользователя или аналогичным. Вы не упоминаете, которые SQL вкус Youre использованием, но в 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

1

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

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

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

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

2

Это то, на что предназначены BEGIN TRAN и COMMIT TRAN. Поместите заявления, которые вы хотите защитить в рамках транзакции.

0

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

0

SQL сам по себе не имеет переменных, но (почти?) Все RDBMS SQL-расширения делают. Но я не уверен, как это решит вашу проблему.

Как уже упоминалось, сделка будет делать трюк - эффективно группировать ваши 2 несвязанных заявления вместе. Однако, уровень транзакции по умолчанию будет не работает. (Большинство?) Уровень транзакции по умолчанию для сервера РСУБД 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"); 

В зависимости от конкретных заявлений, вы можете быть в состоянии уйти только с повторением «UserId IS NULL» ИНЕК в вашем UPDATE заявлении. Это похоже на решение Бримстедта - но вы все равно должны проверить @@ ROWCOUNT, чтобы увидеть, действительно ли строки были обновлены.

Смежные вопросы