2008-09-11 6 views
13

Éste tomará algunas explicaciones. Lo que he hecho es crear una cola de mensajes personalizada específica en SQL Server 2005. Tengo una tabla con mensajes que contienen marcas de tiempo tanto para confirmación como para finalización. El procedimiento almacenado que ejecutan los llamadores para obtener el siguiente mensaje en su cola también reconoce el mensaje. Hasta aquí todo bien. Bueno, si el sistema está experimentando una gran cantidad de transacciones (miles por minuto), ¿no es posible que un mensaje sea confirmado por otra ejecución del procedimiento almacenado mientras otro está preparado para hacerlo? Dejarme ayudar al mostrar mi código SQL en el procedimiento almacenado:¿Cómo se bloquean las tablas en SQL Server 2005, e incluso debería hacerlo?

--Grab the next message id 
declare @MessageId uniqueidentifier 
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands); 

--Acknowledge the message 
update ActionMessages 
set AcknowledgedTime = getdate() 
where ActionMessageId = @MessageId 

--Select the entire message 
... 
... 

En el código anterior, no pudo otro procedimiento almacenado se ejecuta al mismo tiempo obtener el mismo identificador y tratar de reconocer que al mismo tiempo? ¿Podría (o debería) implementar algún tipo de bloqueo para evitar que otro proceso almacenado reconozca los mensajes que otro proceso almacenado está consultando?

Guau, ¿algo de esto tiene sentido? Es un poco difícil poner en palabras ...

+0

es posible que desee volver a etiquetar esto para que "bestpractice" cambie a las "mejores prácticas" más comúnmente utilizadas – martinatime

Respuesta

7

Algo como esto

--Grab the next message id 
begin tran 
declare @MessageId uniqueidentifier 
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands with(holdlock, updlock); 

--Acknowledge the message 
update ActionMessages 
set AcknowledgedTime = getdate() 
where ActionMessageId = @MessageId 

-- some error checking 
commit tran 

--Select the entire message 
... 
... 
-1

Desea ajustar su código en una transacción, SQL Server se encargará de bloquear las filas o tablas correspondientes.

begin transaction 

--Grab the next message id 
declare @MessageId uniqueidentifier 
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands); 

--Acknowledge the message 
update ActionMessages 
set AcknowledgedTime = getdate() 
where ActionMessageId = @MessageId 

commit transaction 

--Select the entire message 
... 
+0

¿Sería así de simple ... si un bloqueo (compartido) se mantiene por la primera declaración dependerá en el nivel de aislamiento de transacción, y en el mejor de los casos (si el nivel de aislamiento es tal que se mantienen los bloqueos compartidos), ese código dará lugar a interbloqueos ya que dos conexiones diferentes recibirán el mismo mensaje y ambos intentarán actualizarlo (cada uno impedido haciéndolo por la cerradura del otro). – Tao

0

¿Realmente debería procesar las cosas una a una? ¿No debería simplemente hacer que SQL Server reconozca todos los mensajes no reconocidos con la fecha de hoy y los devuelva? (Todo también en una transacción por supuesto)

+0

¿No es eso lo que estoy haciendo? SQL Server reconoce los mensajes en nombre de la persona que llama y los devuelve. – Kilhoffer

+0

No, quise decir algo como esto (no probado): BEGIN TRANSACTION SELECT * FROM UncknowledgedDemands; ActionMessages ACTUALIZACIÓN SET AcknowledgetTime = getdate() donde existe (SELECT * FROM UnacknowledgedDemands DONDE ActionMessages.ActionMessageId = UnacknowledgedDemands.ActionMessageId) transacción de confirmación – rpetrich

1

@Kilhoffer:

Todo el lote SQL se analiza antes de la ejecución, por lo que SQL sabe que usted va a hacer una actualización de la tabla, así como seleccionar de ella .

Editar: Además, SQL no necesariamente bloqueará toda la tabla, simplemente podría bloquear las filas necesarias. Consulte here para obtener una descripción general del bloqueo en el servidor SQL.

+0

no estoy de acuerdo con eso. El análisis no tiene nada que ver con eso. Si lo hiciera, el SQL ejecutado a través de EXECUTE no sería seguro para las transacciones. –

+0

Pero no hay EJECUTAR aquí. Buen punto, ¿cómo sabe SQL qué bloquear cuando se ejecuta SQL arbitrario mediante la ejecución? – Blorgbeard

+0

El servidor SQL decide qué bloquear y si mantener los bloqueos o liberarlos, tal como sucede (ya que ejecuta cada instrucción en la transacción), NO en el tiempo de análisis. Por lo tanto, no hay diferencia entre las llamadas EXEC y un lote normal. La única diferencia estaría en la elección del plan de consulta (si usar un almacenado en caché o no). – Tao

0

Leer más sobre SQL Server Seleccione Bloqueo here y here. SQL Server tiene la capacidad de invocar un bloqueo de tabla en una selección. Nada sucederá en la mesa durante la transacción. Cuando la transacción se complete, las inserciones o actualizaciones se resolverán solos.

+1

¿Esto quiere decir que simplemente ajustando el código anterior en una transacción, SQL Server implementa de hecho el bloqueo de selección como se describe en los enlaces anteriores? – Kilhoffer

1

En vez de bloqueo explícita, que a menudo se intensificó por SQL Server a una mayor granularidad de lo deseado, ¿por qué no tratar este enfoque:

declare @MessageId uniqueidentifier 
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands 

update ActionMessages 
    set AcknowledgedTime = getdate() 
    where ActionMessageId = @MessageId and AcknowledgedTime is null 

if @@rowcount > 0 
    /* acknoweldge succeeded */ 
else 
    /* concurrent query acknowledged message before us, 
    go back and try another one */ 

Cuanto menos se bloquea - la mayor concurrencia que tiene.

2

Este parece ser el tipo de situación en la OUTPUT pueden ser útiles:

-- Acknowledge and grab the next message 
declare @message table (
    -- ...your `ActionMessages` columns here... 
) 
update ActionMessages 
set AcknowledgedTime = getdate() 
output INSERTED.* into @message 
where ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands) 
    and AcknowledgedTime is null 

-- Use the data in @message, which will have zero or one rows assuming 
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question) 
... 
... 

Hay, actualizamos y agarrar la fila en la misma operación, que le dice al optimizador de consultas exactamente lo que estamos haciendo , lo que le permite elegir la cerradura más granular que pueda y mantenerla por el menor tiempo posible. (Aunque el prefijo de columna es INSERTED, OUTPUT es como desencadenantes, expresado en términos de UPDATE, es como borrar la fila e insertar la nueva.)

que necesitaría más información sobre sus ActionMessagesUnacknowledgedDemands y tablas (vistas/TVF/lo que sea), por no mencionar un mayor conocimiento de bloqueo automático de SQL Server, para decir si esa cláusula and AcknowledgedTime is null es necesario. Está ahí para defenderse contra una condición de carrera entre la selección secundaria y la actualización. Estoy seguro de que no sería necesario si seleccionáramos desde el propio ActionMessages (por ejemplo, where AcknowledgedTime is null con top en el update, en lugar de seleccionar de UnacknowledgedDemands). Espero que incluso si es innecesario, es inofensivo.

Tenga en cuenta que OUTPUT está en SQL Server 2005 y posteriores. Eso es lo que dijiste que estabas usando, pero si se requería compatibilidad con las instalaciones geriátricas de SQL Server 2000, querrías ir por otro camino.

+0

(Muy tarde para la fiesta, pero vi que nadie lo había mencionado, así que ...) –

Cuestiones relacionadas