¿Cómo limito un procedimiento almacenado de SQL para que lo ejecute una persona a la vez?

12

Tengo un procedimiento almacenado que básicamente selecciona valores de una tabla y los inserta en otra, una especie de archivo. Quiero evitar que varias personas hagan eso al mismo tiempo.

Mientras se ejecuta este procedimiento, no quiero que nadie más pueda iniciarlo; sin embargo, no quiero la serialización, la otra persona ejecutará el procedimiento una vez que haya terminado con él.

Lo que quiero es que la otra persona que intenta iniciarlo obtenga un error, mientras estoy ejecutando el procedimiento.

He intentado usar sp_getapplock, sin embargo, no puedo detener por completo a la persona para que no ejecute el procedimiento.

También intenté encontrar el procedimiento con sys.dm_exec_requests y bloquear el procedimiento, aunque esto funciona, creo que no es óptimo porque en algunos servidores no tengo los permisos para ejecutar sys.dm_exec_sql_text (sql_handle).

¿Cuál es la mejor manera de hacer esto?

twoheadedmona
fuente
3
¿Puede dar un paso atrás y proporcionar más información sobre lo que está haciendo el procedimiento y por qué desea evitar que varias personas lo ejecuten al mismo tiempo? Puede haber una técnica de codificación que elimine este requisito, o algún tipo de cola que podría implementar para manejar las cosas.
AMtwo

Respuestas:

15

Para agregar a la respuesta de @ Tibor-Karaszi, establecer un tiempo de espera de bloqueo en realidad no produce un error (he enviado un RP contra los documentos). sp_getapplock solo devuelve -1, por lo que debe verificar el valor de retorno. Así como esto:

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end
David Browne - Microsoft
fuente
8

Use sp_getapplock al comienzo del proceso y establezca un tiempo de espera de bloqueo en un valor muy bajo. De esta forma, obtienes un error cuando estás bloqueado.

Tibor Karaszi
fuente
7

Otra opción es construir una tabla para controlar el acceso al procedimiento. El siguiente ejemplo muestra una posible tabla, así como un procedimiento que podría utilizarla.

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END
Jonathan Fite
fuente
1
Esto es muy similar (o quizás el mismo) a lo que pensé inmediatamente después de leer la pregunta, pero tuve un problema con esa idea de que no estaba completamente seguro de cómo abordarlo y no puedo verlo en su respuesta ya sea. Mi preocupación es, ¿qué pasa si algo sucede durante la parte de "hacer lo que sea necesario hacer"? ¿Cómo restablecería el IsLockedestado a 0 en ese caso? También tengo curiosidad sobre su uso de COALESCEaquí. Puede @@ROWCOUNTser nulo después de declaraciones como UPDATE? Finalmente, solo un pequeño detalle, ¿por qué poner un punto y coma delante de la THROWdeclaración en ese caso específico?
Andriy M
La caducidad de la cerradura es una forma de manejar eso. Tendría que configurarlo en un plazo razonable, en mi ejemplo lo configuré en 10 minutos. Puede encapsular su lógica de trabajo en un bloque try / catch y desbloquearlo en el catch si así lo desea. Uso COALESCE por costumbre, pero no @@ ROWCOUNT no puede ser NULL. el punto y coma principal proviene de trabajar con proyectos de bases de datos de Visual Studio, se queja si no está allí. No hay daño de ninguna manera.
Jonathan Fite
-1

Creo que estás tratando de resolver el problema de manera incorrecta. Lo que desea es la máxima protección de la coherencia de la base de datos. Si dos personas ejecutan un procedimiento almacenado al mismo tiempo, se puede violar la coherencia de la base de datos.

Para protegerse contra varios tipos de inconsistencias de la base de datos, el estándar SQL tiene cuatro niveles de aislamiento de transacciones:

  • LEA SIN COMPROMISO donde básicamente las transacciones pierden su valor, otras transacciones ven datos sucios. ¡No uses esto!
  • LEA COMPROMETIDO donde las transacciones solo ven datos confirmados, pero puede haber inconsistencias en las que dos transacciones pueden pasar por encima de los pies del otro
  • LECTURA REPETIBLE donde se resuelve un tipo de inconsistencia, lectura no repetible
  • SERIALIZABLE que garantiza que existe algún orden virtual en el que la ejecución de las transacciones conduciría a los resultados que su ejecución resultó en

Sin embargo, el estándar SQL tiene un enfoque basado en el bloqueo para estas inconsistencias de la base de datos, y por razones de rendimiento, muchas bases de datos adoptan un enfoque basado en el aislamiento de instantáneas que básicamente tiene estos niveles:

  • LEA COMPROMETIDO, que es el mismo que está en el bloqueo de bases de datos basadas
  • INSTANTÁNEA DE AISLAMIENTO donde la base de datos ve una instantánea de todos los datos y si intenta actualizar una fila que ha sido actualizada por alguna otra transacción, se cancela, pero hay algunas anomalías bien conocidas que pueden tener lugar
  • SERIALIZABLE, que es lo mismo que en el bloqueo de bases de datos basadas, pero esta vez implementado de una manera diferente, no mediante bloqueos sino asegurando que no haya violaciones de serialización, y si se detecta dicha violación, cancelando una transacción

Las cancelaciones de transacciones en estas bases de datos basadas en el aislamiento de instantáneas pueden parecer preocupantes, pero una vez más, cada base de datos cancelará una transacción debido a un punto muerto, por lo que cualquier aplicación razonable necesita de todos modos poder volver a intentar una transacción.

Lo que desea es el nivel de aislamiento SERIALIZABLE : asegura que si las transacciones ejecutadas independientemente una tras otra dan como resultado un buen estado, cualquier ejecución paralela de las transacciones también resulta en un buen estado. Afortunadamente, Michael Cahill descubrió en su disertación doctoral cómo el nivel de aislamiento SERIALIZABLE puede ser soportado por bases de datos aisladas instantáneas con poco esfuerzo.

Si utiliza un nivel de aislamiento SERIALIZABLE en una base de datos aislada de instantánea, si dos personas intentan ejecutar el procedimiento almacenado al mismo tiempo y se pisarían los pies, la transacción se cancelaría.

Ahora, ¿SQL Server admite realmente el nivel de aislamiento SERIALIZABLE (en lugar de enmascarar el aislamiento de la instantánea detrás de la palabra clave SERIALIZABLE )? Francamente, no lo sé: la única base de datos que sé que lo admite es PostgreSQL.

Aunque no pude dar consejos específicos de SQL Server, todavía estoy publicando esta respuesta, ya que los usuarios de PostgreSQL y los usuarios de otras bases de datos que pueden considerar cambiar a PostgreSQL pueden beneficiarse de mi respuesta. Además, los usuarios de bases de datos que no son de PostgreSQL que no pueden cambiar a PostgreSQL pueden presionar a su proveedor de bases de datos favorito para que ofrezca un nivel de aislamiento SERIALIZABLE genuino .

juhist
fuente
Supongo que el voto negativo significa que alguien investigó si SQL Server tiene el nivel de aislamiento SERIALIZABLE y descubrió que no.
juhist
-2

Me doy cuenta de que el problema "real" puede ser más complejo.

En caso de que no lo sea: si archiva con desencadenadores de inserción y / o actualización, puede evitar el problema que está tratando de resolver.

Espero que ayude,
-Chris C.

J. Chris Compton
fuente
1
¿Qué quieres decir con "inmediatamente"? ¿Inmediatamente después de qué? Después de insertar? ¿Tan pronto como llega una nueva fila, se envía inmediatamente al archivo? ¿O quiso decir después de la actualización? Entonces, ¿cualquier cambio de datos desencadena el archivado? Quizás debería ser más específico sobre qué escenario tiene en mente al sugerir esto.
Andriy M
El archivo puede ser demasiado costoso y / o raramente deseado para que valga la pena hacerlo en cada inserción, especialmente si la tabla fuente se inserta con frecuencia y / o la seguridad de las transacciones entre ella y el archivo requeriría costosos bloqueos.
underscore_d
@underscore_d Sí, puede ser demasiado costoso o no siempre se requiere. Por eso comencé mi respuesta con la afirmación de que the 'real' problem may be more complex. En caso de que no lo sea, los desencadenantes son una buena solución. Además, probablemente será más fácil de probar y mantener porque es una característica de la base de datos en lugar de una solución personalizada.
J. Chris Compton
@AndriyM Eliminé la palabra inmediatamente, reemplazándola por una referencia para insertar / actualizar disparadores. Perdón por la confusion.
J. Chris Compton
1
He releído la pregunta y creo que puedo ver la fuente de mi confusión. Lo que está sugiriendo aquí es más parecido a la auditoría que al archivo. Según tengo entendido, el archivo de datos implica mover los datos (por ejemplo, de una tabla a otra). Sin embargo, a pesar de que el OP resumió la función de su procedimiento como "una especie de archivo", nunca dijeron que los datos serían eliminados de la fuente, solo que serían seleccionados e insertados en el destino. Así que supongo que asumió que el OP necesita copiar , en lugar de mover , sus datos, en cuyo caso el uso de disparadores probablemente tenga sentido.
Andriy M