2010-09-03 12 views
6

Necesito tener una tabla de base de datos MsSql y otros 8 procesos (idénticos) accediendo a la misma tabla en paralelo - haciendo una selección superior n, procesando esas n filas, y actualizando una columna de esas filas. El problema es que necesito seleccionar y procesar cada fila solo una vez. Esto significa que si un proceso llegó a la base de datos y seleccionó las n primeras filas, cuando llegue el segundo proceso debería encontrar esas filas bloqueadas y seleccionar las filas de n a 2 * n filas, y así sucesivamente ...Retorno de filas desbloqueadas en una consulta "seleccionar top n"

¿Es posible poner un candado en algunas filas cuando las selecciona, y cuando alguien solicita las primeras n filas que están bloqueadas para devolver las siguientes filas, y no esperar las bloqueadas? Parece una posibilidad remota, pero ...

Otra cosa que estaba pensando - tal vez no tan elegante, pero suena simple y seguro, es tener en la base de datos un contador para las instancias que hicieron selects en esa tabla. La primera instancia que aparece aumentará el contador y seleccionará la parte superior n, la siguiente incrementará el contador y seleccionará filas de n * (i-1) a n * i, y así sucesivamente ...

¿Esto suena? como un buen ideea? ¿Tienes alguna mejor sugerencia? ¡Cualquier pensamiento es muy apreciado!

Gracias por su tiempo.

+3

Parece que está utilizando una tabla como cola? Puede encontrar [este artículo] (http: // rusanu.com/2010/03/26/using-tables-as-queues /) por [Remus Rusanu] (http://stackoverflow.com/users/105929/remus-rusanu) útil. –

+0

Gracias por el enlace. Fue interesante saber sobre eso, pero como necesito mantener las filas después de seleccionarlas (borrarlas no es una opción), no es realmente aplicable a mi caso. – Diana

+1

Puede tener una columna de bits 'procesada' y sustituir una' ACTUALIZACIÓN' por un 'DELETE' posiblemente. Si decide usar una solución de contador [esta respuesta] (http://stackoverflow.com/questions/3453411/sql-server-auto-incrementation-hathat-allows-update-statements/3462957#3462957) podría ayudar. –

Respuesta

6

He aquí una muestra I blogged about a while ago:

La sugerencia READPAST es lo que asegura múltiples procesos no bloquean entre sí cuando los registros de votación para procesar. Además, en este ejemplo, tengo un campo de bit para "bloquear" físicamente un registro; podría ser una fecha y hora si fuera necesario.

DECLARE @NextId INTEGER 
BEGIN TRANSACTION 

-- Find next available item available 
SELECT TOP 1 @NextId = ID 
FROM QueueTable WITH (UPDLOCK, READPAST) 
WHERE IsBeingProcessed = 0 
ORDER BY ID ASC 

-- If found, flag it to prevent being picked up again 
IF (@NextId IS NOT NULL) 
    BEGIN 
     UPDATE QueueTable 
     SET IsBeingProcessed = 1 
     WHERE ID = @NextId 
    END 

COMMIT TRANSACTION 

-- Now return the queue item, if we have one 
IF (@NextId IS NOT NULL) 
    SELECT * FROM QueueTable WHERE ID = @NextId 
+0

Gracias por su respuesta, READPAST suena como la respuesta MsSql que estaba buscando. Si SELECT TOP 1 @NextId = ID es una selección superior de 3000 en varias tablas de millones de registros, podría llevar algo de tiempo. ¿Está "garantizado" por READPAST que si tengo otro hilo haciendo exactamente la misma selección, el segundo recogerá las siguientes filas desbloqueadas? – Diana

+0

He probado su enfoque en una tabla de prueba simple, funciona como un encanto. Pero en mi mesa real (muy grande, muchos índices y estadísticas), solo funciona a veces ... Otras veces, no omite las filas bloqueadas, simplemente se "cuelga" hasta que se cierra la selección previa de bloqueo de las filas (Para probar esto agregué aw "aitfor delay '00: 00: 10 '"). ¿Alguna idea de por qué está sucediendo esto? – Diana

+0

@Diana - ¿Cuántas filas está intentando bloquear por proceso? p.ej. quizás se esté tomando un bloqueo de tabla (rowlocks se actualizará a bloqueos de tabla por encima de un cierto umbral). Si pudiera publicar un script de ejemplo que demuestre cómo lo está haciendo (como obvio, mi ejemplo fue para un solo registro), sería genial; podría valer la pena en una pregunta vinculada para una exposición máxima – AdaTheDev

2

El método más sencillo es utilizar row locking:

BEGIN TRAN 

SELECT * 
FROM authors 
WITH (HOLDLOCK, ROWLOCK) 
WHERE au_id = '274-80-9391' 

/* Do all your stuff here while the record is locked */ 

COMMIT TRAN 

Pero si se accede a sus datos y luego cerrar la conexión, usted no será capaz de utilizar este método.

¿Cuánto tiempo necesitará bloquear las filas? La mejor manera podría ser, como dice, colocar un contador en las filas que seleccione (mejor hecho usando la cláusula OUTPUT dentro de un UPDATE).

+0

Necesito cerrar la conexión después de la selección, y procesar el lote de filas llevará algo de tiempo (cada fila determinará el envío de un correo electrónico con un archivo adjunto de 500kb). – Diana

+0

Diría que necesitas implementar tu propio método para bloquear las filas (como el contador) ya que el bloqueo de filas puede bloquear las solicitudes el tiempo suficiente para que aparezcan errores de tiempo de espera en otro lugar. El comentario de Martin parece un camino a seguir http://stackoverflow.com/questions/3636950/return-unlocked-rows-in-a-select-top-n-query#comment-3822218 – Codesleuth

0

La mejor idea si desea seleccionar registros de esta manera sería utilizar un contador en una tabla separada.

Realmente no desea estar bloqueando filas en una base de datos de producción exclusivamente por un gran período de tiempo, por lo tanto, recomendaría usar un contador. De esta forma, solo uno de sus procesos podrá obtener ese número de contador a la vez (ya que se bloqueará a medida que se actualice), lo que le proporcionará la concurrencia que necesita.

Si necesita una mano que escriba las tablas y los procedimientos que lo harán (¡de manera sencilla y segura como usted lo mencionó!) Solo pregunte.

+0

Ok, me alegro de ver que la idea de el contador suena lo suficientemente bueno y no se pueden ver flujos importantes a primera vista. Lo pensaré más por un día más o menos. ¡Gracias por su rápida respuesta y ayuda! – Diana

0

EDITAR: ahh, nevermind, estás trabajando en un estilo desconectado. ¿Qué tal esto:

UPDATE TOP (@n) QueueTable SET Locked = 1 
OUTPUT INSERTED.Col1, INSERTED.Col2 INTO @this 
WHERE Locked = 0 

<do your stuff> 

Tal vez usted está buscando el READPAST pista?

<begin or save transaction> 

INSERT INTO @this (Col1, Col2) 
SELECT TOP (@n) Col1, Col2 
FROM Table1 WITH (ROWLOCK, HOLDLOCK, READPAST) 

<do your stuff> 

<commit or rollback> 
Cuestiones relacionadas