Pregunta

Estoy escribiendo un programa de mensajería simple, donde hay una tabla de mensajes, que pueden ser reclamados por los usuarios y se han hecho las cosas a ellos por ese usuario. No está predestinado qué usuario se cobrará un mensaje dado y por eso quiero una consulta para seleccionar el primero de todos los mensajes disponibles, que tengo, y luego uno para marcar el mensaje como toma, que también tengo. El problema es que no quiero dos usuarios utilizando al mismo tiempo para reclamar el mismo mensaje y lo que desee ejecutar las dos declaraciones de forma consecutiva, sin tener que pasar de nuevo al programa para averiguar lo que se ejecute en el próximo entre los enunciados. Creo que puedo ejecutar dos estados consecutivos separándolos con punto y coma, pero quiero usar datos devueltos en la primera consulta como parte de la segunda. Las variables sería perfecto, pero por lo que yo sepa que no existen en SQL. ¿Hay alguna manera de preservar el estado entre las consultas?

¿Fue útil?

Solución

Las transacciones es una buena manera de ir, como le dice dorfier, pero hay alernatives:

Usted puede hacer la actualización en primer lugar, es decir, el etiquetado de un mensaje con el identificador de usuario o similar. Usted no menciona lo que usted está utilizando SQL sabor, pero en MySQL, creo que sería semejante a lo siguiente:

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

En ms sql, que sería algo a lo largo de las líneas de:

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

/ B

Otros consejos

Esto es lo COMENZAR TRAN COMMIT TRAN y son para. Colocar las declaraciones que desea proteger dentro de una transacción.

  

¿Hay alguna manera de preservar el estado entre las consultas?

No. SQL no es un lenguaje de procedimientos. Puede volver a escribir sus dos consultas como una sola consulta (no siempre es posible, a menudo no vale la pena incluso si es posible), o pegarlas con un lenguaje de procedimientos. Muchos servidores SQL proporcionan un lenguaje incorporado para esto ( "Procedimientos almacenados"), o puede hacerlo en su aplicación.

  

El problema es que no quiero dos usuarios que lo utilizan al mismo tiempo para reclamar el mismo mensaje

Use cerraduras. No sé qué servidor SQL que está utilizando, pero utilizando SELECT ... FOR UPDATE suena como que sería justo lo que quieres, si está disponible.

Se puede usar una tabla temporal, tal vez.

SQL en sí no tiene las variables, pero (casi?) Todas las extensiones de SQL RDBMS hacer. Sin embargo, no estoy muy seguro de cómo que por sí solo resolver su problema.

Como se ha mencionado, una transacción hará el truco - agrupar de manera efectiva sus declaraciones 2 no relacionadas entre sí. No obstante , el nivel de transacción por defecto será no funciona . (La mayoría?) Nivel de transacción del servidor RDBMS se Lectura confirmada. Eso no impide que el usuario 2 de la lectura de la misma fila que el usuario lea 1. Para ello, se necesitaría utilizar LEER REPETIBLE o SERIALIZABLE.

Este es un tema clásico de la concurrencia. En general, las 2 formas de manejar que son el bloqueo pesimista o comprobación optimista. Una transacción de lectura REPETIBLE sería pesimista (incurrir en el gasto de bloqueo si es o no era necesario), y la comprobación de @@ ROWCOUNT es optimista (suponiendo que va a trabajar, pero haciendo algo sensato cuando @@ ROWCOUNT = 0).

Por lo general, se utiliza optimista (bloqueo es caro), y, o bien usar una marca de tiempo o combinación de campos de lectura para asegurar que estamos cambiando los datos que pensábamos. Por lo tanto, mi sugerencia es incluir un campo rowversion o marca de tiempo, y pasadas de vuelta a su instrucción UPDATE. A continuación, compruebe @@ ROWCOUNT para ver si ha actualizado ningún registro. Si no lo hizo, y luego volver y recoger otro mensaje. En pseudo-código:

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 función de sus estados particulares, puede ser capaz de salirse con sólo repetir la "identificación de usuario ES NULO" cláusula where de la instrucción UPDATE. Eso es similar a la solución de Brimstedt - pero No obstante, debe comprobar @@ ROWCOUNT para ver si realmente se actualizan filas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top