Usar banco de dados para rastrear o bloqueio / desbloqueio de objetos

8

Eu tenho um requisito para rastrear ações de bloqueio / desbloqueio de objetos. Antes de qualquer ação realizada em um objeto (contrato, parceiro, etc.), um lockevento é emitido. Depois que a ação é concluída, ela emite o unlockevento.

Quero obter os objetos que estão bloqueados, mas ainda não desbloqueados. O objetivo é tornar a consulta rápida e evitar conflitos.

Abaixo está a tabela

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)

Eu uso a consulta abaixo para objetos ainda não 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 correto e resulta a, be d.

Minhas perguntas são: - Minha solução é suficiente para evitar conflitos? Existe algum problema que pode ocorrer se houver muitos INSERTdurante a execução da consulta? - Você tem outra (melhor) maneira de resolver isso?

ATUALIZAR

Peço desculpas por não colocar o contexto em questão. O design do banco de dados acima não serve para substituir o bloqueio do banco de dados.

Temos um sistema externo que chamamos de nosso sistema. Requer chamar locke unlockmétodo em seus sistemas antes de cada ação executada em um objeto (poderia ser um contrato ou um parceiro).

Recentemente, temos situações em que o servidor trava e precisamos reiniciá-lo. Infelizmente, os processos em execução que já chamavamlock não tiveram a chance de chamar unlockpara liberar os objetos, levando a vários outros problemas quando nosso sistema se conectou novamente ao externo.

Portanto, queremos fornecer um recurso para rastrear cada lockchamada. Ao reiniciar o servidor, chamaremosunlock os objetos que foram bloqueados anteriormente.

Obrigado Remus Rusanu por apontar que minha pergunta está usando um protótipo DDL. Esta é a primeira vez que postei uma pergunta no DBA e peço desculpas por não ter lido as Perguntas frequentes.

obrigado

Genzer
fonte

Respostas:

11

O objetivo é tornar a consulta rápida e evitar conflitos.

Este é um objetivo irrealista. Os conflitos são determinados pelo aplicativo que obtém os bloqueios e estão fora de controle de como você implementa o bloqueio. O melhor que você pode esperar é detectar impasses.

A implementação de bloqueios como registro é problemática. As linhas persistem e você vazará bloqueios na falha do aplicativo, mesmo quando a implementação for perfeita . Faça bloqueios com apliques . Eles têm semântica transacional clara e os deadlocks são detectados pelo mecanismo.

No entanto, olhando para o que você está tentando alcançar, é improvável que você precisa fechaduras em tudo . Você está descrevendo uma fila (escolha o próximo item disponível para processar e evitar conflitos => fila, sem bloqueio). Leia Usando tabelas como filas .

Quanto à sua implementação específica (usando uma tabela que mantém o histórico do bloqueio), devo ser sincero: é um desastre. Para começar com o design da tabela, é completamente inadequado para o uso pretendido: você consulta pelo nome, mas a tabela é uma pilha sem índices. Ele possui uma coluna de identidade sem motivo aparente. Você pode responder que é apenas uma tabela de 'pseudo-código', mas esta é DBA.SE, você não publica aqui DDL incompleto!

Mas o mais importante é que a implementação não implementa o bloqueio! Não há nada para impedir que dois usuários 'bloqueiem' o mesmo objeto duas vezes. Seu 'bloqueio' depende inteiramente dos chamadores que se comportam magicamente corretamente. Mesmo um melhor aplicativo escrito não pode usar esse bloqueio, porque não há como verificar e adquirir o bloqueio atomicamente . Dois usuários podem verificar, concluir que 'a' está desbloqueado e inserir simultaneamente o ('a', 1)registro. No mínimo, você precisaria de uma restrição única. O que obviamente quebraria a semântica de "contar os bloqueios x os desbloqueios para determinar o status".

Lamento dizer, mas esta é uma implementação de grau F.

ATUALIZAR

Portanto, queremos fornecer um recurso para rastrear cada chamada de bloqueio. Ao reiniciar o servidor, chamaremos unlock nos objetos que foram bloqueados anteriormente.

Sem envolver-se em uma transação distribuída de confirmação em duas fases com o sistema remoto, tudo o que você pode fazer é um 'melhor esforço', porque há muitas condições de corrida entre você escrever o 'unlock' e realmente chamar 'unlock' no sistema de terceiros . Como melhor esforço, eis a minha recomendação:

  • crie uma tabela simples para rastrear os bloqueios:

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

  • Antes de chamar, lockinsira o bloqueio em sua tabela e confirme .

  • depois de chamar, unlock exclua o bloqueio da sua tabela e confirme
  • na inicialização do sistema, olhe para a mesa. qualquer linha existe uma trava sobra de uma execução anterior e deve ser 'desbloqueada'. Ligue unlockpara cada linha e exclua a linha. Somente depois que todos os bloqueios pendentes tiverem sido "desbloqueados", você poderá retomar a funcionalidade normal do aplicativo.

Estou propondo isso porque a tabela permanecerá pequena. A qualquer momento, ele contém apenas bloqueios ativos e atuais. Não crescerá ad-nauseam e causará problemas mais tarde devido ao tamanho total. É trivial ver o que está bloqueado.

Obviamente, essa implementação não oferece um histórico de auditoria do que foi bloqueado por quem e quando. Você pode adicionar isso, se necessário, como uma tabela diferente, na qual você insere apenas os eventos (bloquear ou desbloquear) e nunca consultar isso para descobrir bloqueios 'órfãos'.

Você ainda precisa estar preparado com chamadas para 'desbloquear' para falhar durante a inicialização, porque não pode garantir que seu sistema não travou após a chamada, unlockmas antes de excluir a linha (em outras palavras, sua tabela e o sistema de terceiros se separaram e têm diferentes versões da verdade). Mais uma vez você não pode evitar isso w / o transações distribuídas e eu nunca aconselharia para DTC.

Remus Rusanu
fonte
4

Isso eliminará a simultaneidade no seu aplicativo. O SQL Server já possui tudo o que é necessário para evitar a atualização das mesmas linhas ao mesmo tempo, portanto, não há realmente necessidade disso.

Os conflitos podem ocorrer por vários motivos, por isso sugiro que você os investigue antes de criar seu próprio sistema de bloqueio. Você pode capturar gráficos de deadlock na sessão XE de integridade do sistema e começar a analisá-los.

Geralmente, os deadlocks são o resultado de bloquear bloqueios em objetos em ordens diferentes, portanto, você deve tentar bloqueios sempre na mesma ordem. Há outras razões pelas quais os conflitos podem ocorrer, mas a orientação geral é que transações curtas retêm bloqueios por um curto período de tempo, portanto, ajustar suas consultas com melhores códigos, índices e estratégias de divisão e impera provavelmente será a melhor maneira de se livrar de impasses.

Os tipos de deadlocks "Leitores bloqueando gravadores" podem ser altamente mitigados alternando o banco de dados para Isolamento de instantâneo confirmado por leitura . Se seu aplicativo foi criado com o bloqueio pessimista em mente, você deve revisar e testar tudo com muito cuidado antes de ativar esta opção no seu banco de dados.

Caso você insista em seguir a rota "sistema de bloqueio personalizado", use pelo menos algo que garanta a liberação de bloqueios, caso algo com o aplicativo dê errado. Convém investigar o procedimento armazenado sp_getapplock interno, que faz algo semelhante ao que você parece estar procurando.

ATUALIZAÇÃO: Depois de ler sua pergunta editada, esta é uma maneira alternativa de expressar a mesma 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á se for permitido que os objetos sejam bloqueados apenas uma vez, o que parece ser o objetivo do sistema de bloqueio.

spaghettidba
fonte