Use la base de datos para rastrear el bloqueo / desbloqueo de objetos

8

Tengo un requisito para rastrear acciones de bloqueo / desbloqueo de objetos. Antes de cualquier acción realizada sobre un objeto (contrato, socio, etc.), lockse emite un evento. Una vez finalizada la acción, emite el unlockevento.

Quiero obtener esos objetos que están bloqueados pero aún no desbloqueados. El objetivo es agilizar la consulta y evitar puntos muertos.

Debajo está la tabla

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

Utilizo la siguiente consulta para objetar objetos aún no desbloqueados:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Funciona correcto y el resultado a, by d.

Mis preguntas son: - ¿Es mi solución lo suficientemente suficiente como para evitar puntos muertos? ¿Hay algún problema que pueda ocurrir si hay muchos INSERTdurante la ejecución de la consulta? - ¿Tienes alguna otra (mejor) forma de resolver esto?

ACTUALIZAR

Pido disculpas por no poner el contexto en la pregunta. El diseño de la base de datos anterior no es para reemplazar el bloqueo de la base de datos.

Tenemos un sistema externo que lo llamamos desde nuestro sistema. Se requiere llamar locky unlockmétodo en sus sistemas antes de cada acción que se realiza en un objeto (podría ser un contrato o un socio).

Recientemente, tenemos situaciones en las que el servidor falla y tenemos que reiniciarlo. Desafortunadamente, los procesos en ejecución que ya llamaron lockno tuvieron la oportunidad de llamar unlockpara liberar los objetos, por lo que provocaron varios otros problemas cuando nuestro sistema se conecta nuevamente al externo.

Por lo tanto, queremos proporcionar una capacidad para rastrear cada lockllamada. Al reiniciar el servidor, llamaremos unlocka los objetos que anteriormente estaban bloqueados.

Gracias a Remus Rusanu por señalar que mi pregunta es usar un prototipo DDL. Esta es la primera vez que publico una pregunta en DBA y pido disculpas por no leer las preguntas frecuentes.

Gracias

Genzer
fuente

Respuestas:

11

El objetivo es agilizar la consulta y evitar puntos muertos.

Este es un objetivo poco realista. Los bloqueos están determinados por la aplicación que adquiere los bloqueos y está fuera de control de cómo implementar el bloqueo. Lo mejor que puede esperar es detectar puntos muertos.

Implementar bloqueos como registro es problemático. Las filas persisten y perderá bloqueos en el bloqueo de la aplicación, incluso cuando la implementación sea perfecta . Hacer bloqueos con applocks . Tienen una semántica transaccional clara y el motor detecta puntos muertos.

Sin embargo, mirando a lo que estamos tratando de lograr, es poco probable que necesita cerraduras en absoluto . Está describiendo una cola (elija el siguiente elemento disponible para procesar y evite conflictos => cola, no bloqueo). Leer Usar tablas como colas .

En cuanto a su implementación específica (usando una tabla que mantiene el historial del bloqueo), debo ser honesto: es un desastre. Para comenzar, el diseño de la tabla es completamente inadecuado para el uso previsto: consulta por nombre pero la tabla es un montón sin índices. Tiene una columna de identidad sin razón aparente. Puede responder que es solo una tabla de 'pseudocódigo', pero esto es DBA.SE, ¡no publica aquí DDL incompleto!

Pero lo más importante, ¡la implementación no implementa el bloqueo! No hay nada que evite que dos usuarios 'bloqueen' el mismo objeto dos veces. Su "bloqueo" se basa completamente en que las personas que llaman se comporten mágicamente correctamente. Incluso una mejor aplicación escrita no podría usar este bloqueo, porque no hay forma de verificar y adquirir el bloqueo atómicamente . Dos usuarios pueden verificar, concluir que 'a' está desbloqueado e insertar simultáneamente el ('a', 1)registro. Como mínimo , necesitaría una restricción única. Lo que, por supuesto, rompería la semántica de "contar los bloqueos frente a los desbloqueos para determinar el estado".

Lamento decirlo, pero esta es una implementación de grado F.

ACTUALIZAR

Por lo tanto, queremos proporcionar una capacidad para rastrear cada llamada bloqueada. Al reiniciar el servidor, llamaremos a desbloqueo en los objetos que anteriormente estaban bloqueados.

Sin participar en una transacción distribuida de confirmación de dos fases con el sistema remoto, todo lo que puede hacer es un 'mejor esfuerzo', porque hay muchas condiciones de carrera entre que escribe el 'desbloqueo' y realmente llama 'desbloquear' en el sistema de terceros . Como mejor esfuerzo, aquí está mi recomendación:

  • cree una tabla simple para rastrear los bloqueos:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • antes de llamar lockinserte el candado en su tabla y confirme .

  • después de llamar, unlock elimine el bloqueo de su tabla y confirme
  • en el inicio del sistema mira la tabla. en cualquier fila hay un bloqueo restante de una ejecución anterior y debe 'desbloquearse'. Llame unlockpara cada fila, luego elimine la fila. Solo después de que todos los bloqueos pendientes hayan sido 'desbloqueados' puede reanudar la funcionalidad normal de la aplicación.

Estoy proponiendo esto porque la mesa se mantendrá pequeña. En cualquier momento solo contiene bloqueos activos actuales. No crecerá ad nauseam y causará problemas más tarde debido al gran tamaño. Es trivial ver lo que está bloqueado.

Por supuesto, esta implementación no ofrece un historial de auditoría de lo que fue bloqueado por quién y cuándo. Puede agregar eso si es necesario, como una tabla diferente, en la que solo inserte los eventos (bloquear o desbloquear) y nunca consultar eso para descubrir bloqueos 'huérfanos'.

Aún debe estar preparado para que las llamadas para 'desbloquear' fallen durante el inicio porque no puede garantizar que su sistema no se bloqueó después de llamar, unlocksino antes de eliminar la fila (en otras palabras, su mesa y el sistema de terceros se han separado y tienen diferentes versiones de la verdad). Una vez más no se puede evitar que esto w / o transacciones distribuidas y nunca aconsejaría para DTC.

Remus Rusanu
fuente
4

Esto matará la concurrencia en su aplicación. SQL Server ya tiene todo lo necesario para evitar actualizar las mismas filas al mismo tiempo, por lo que realmente no hay necesidad de hacerlo.

Los puntos muertos pueden ocurrir por varias razones, por lo que le sugiero que investigue esas razones antes de construir su propio sistema de bloqueo. Puede capturar gráficos de punto muerto desde la sesión XE del estado del sistema y comenzar a analizarlos.

Por lo general, los interbloqueos son el resultado de bloquear los objetos en diferentes órdenes, por lo que debe intentarlo siempre en el mismo orden. Hay otras razones por las cuales pueden ocurrir puntos muertos, pero la orientación general es que las transacciones cortas mantienen los bloqueos por un corto período de tiempo, por lo que ajustar sus consultas con mejores códigos, índices y estrategias de divide-et-impera probablemente sea la mejor manera de deshacerse de puntos muertos.

Los tipos de interbloqueos de "lectores que bloquean a los escritores" pueden mitigarse en gran medida cambiando la base de datos al aislamiento de instantáneas de lectura confirmada . Si su aplicación se creó teniendo en cuenta el bloqueo pesimista, debe revisar y probar todo con mucho cuidado antes de activar esta opción en su base de datos.

En caso de que insista en seguir la ruta del "sistema de bloqueo personalizado", utilice al menos algo que garantice el desbloqueo de bloqueos en caso de que algo con la aplicación salga mal. Es posible que desee investigar el procedimiento almacenado sp_getapplock incorporado , que hace algo similar a lo que parece estar buscando.

ACTUALIZACIÓN: después de leer su pregunta editada, esta es una forma alternativa de expresar la misma consulta:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Funcionará si se permite que los objetos se bloqueen solo una vez, lo que parece ser el punto principal del sistema de bloqueo de todos modos.

spaghettidba
fuente