2009-03-21 14 views
0

J'écris un programme de messagerie simple, où il y a une table de messages, qui peuvent être réclamés par les utilisateurs et ont des choses faites à eux par cet utilisateur. Il n'est pas prédestiné quel utilisateur va réclamer un message donné et donc je veux une requête pour sélectionner le premier de tous les messages disponibles, ce que j'ai, et ensuite un pour marquer ce message comme prendre, ce que j'ai aussi. Le problème est que je ne veux pas que deux utilisateurs l'utilisent en même temps pour réclamer le même message et je veux donc exécuter les deux instructions consécutivement, sans avoir à revenir au programme pour savoir ce qu'il faut faire ensuite. entre les déclarations. Je crois que je peux exécuter deux instructions consécutives en les séparant avec des points-virgules, mais je veux utiliser les données retournées dans la première requête dans le cadre de la seconde. Les variables seraient parfaites, mais pour autant que je sache, elles n'existent pas en SQL. Est-il possible de conserver l'état entre les requêtes?Instructions SQL consécutives avec l'état

Répondre

0

Transactions est un bon moyen d'aller, comme le dit dorfier, mais il y a alernatives:

Vous pouvez faire la mise à jour, à savoir d'abord un message de marquage avec l'ID utilisateur ou similaire. Vous ne mentionner que vous êtes saveur sql à l'aide, mais dans une base MySQL, je pense que ce serait ressembler à ceci:

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

En ms sql, ce serait quelque chose le long des lignes de:

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

/B

1

Est-il possible de conserver l'état entre les requêtes?

Non. SQL n'est pas un langage procédural. Vous pouvez réécrire vos deux requêtes en une seule requête (ce qui n'est pas toujours possible, cela ne vaut souvent pas la peine même si c'est possible) ou les coller avec un langage procédural. Beaucoup de serveurs SQL fournissent un langage intégré pour cela ("procédures stockées"), ou vous pouvez le faire dans votre application.

Le problème est que je ne veux pas deux utilisateurs qui l'utilisent en même temps de réclamer le même message

Utilisez les verrous. Je ne sais pas quel serveur SQL vous utilisez, mais en utilisant SELECT ... FOR UPDATE semble que ce serait exactement ce que vous voulez, si c'est disponible.

2

C'est ce que BEGIN TRAN et COMMIT TRAN sont pour. Placez les instructions que vous souhaitez protéger dans une transaction.

0

Vous pourriez utiliser une table temporaire peut-être.

0

SQL lui-même n'a pas de variables, mais (presque?) Toutes les extensions SQL RDBMS. Mais, je ne suis pas vraiment sûr que cela suffirait à résoudre votre problème. Comme mentionné, une transaction fera l'affaire - en regroupant efficacement vos 2 déclarations sans lien les unes avec les autres. Toutefois, le niveau de transaction par défaut ne fonctionne pas. (Most?) Le niveau de transaction par défaut du serveur RDBMS est READ COMMITTED. Cela n'empêche pas l'utilisateur 2 de lire la même ligne que l'utilisateur 1. Pour cela, vous devez utiliser REPEATABLE READ ou SERIALIZABLE.

Il s'agit d'un problème de simultanéité classique. Généralement, les deux façons de le manipuler sont le verrouillage pessimiste ou le contrôle optimiste.Une transaction READATABLE READ serait pessimiste (encourant la dépense de verrouillage si elle était nécessaire), et vérifier @@ ROWCOUNT est optimiste (en supposant que cela fonctionne, mais en faisant quelque chose de sensé quand @@ ROWCOUNT = 0).

Habituellement, nous utilisons optimiste (le verrouillage est coûteux), et soit utilisons un horodatage ou une combinaison de champs lus pour nous assurer que nous changeons les données que nous pensions. Donc, ma suggestion est d'inclure un champ rowversion ou timestamp, et de le transmettre à votre instruction UPDATE. Ensuite, vérifiez @@ ROWCOUNT pour voir si vous avez mis à jour des enregistrements. Si vous ne l'avez pas fait, revenez en arrière et choisissez un autre message. En pseudo-code:

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"); 

En fonction de vos déclarations particulières, vous pourriez être en mesure de sortir avec juste répéter la « UserId IS NULL » clause WHERE dans votre instruction UPDATE. C'est similaire à la solution de Brimstedt - mais vous devez toujours vérifier @@ ROWCOUNT pour voir si les lignes ont été réellement mises à jour.