Utilizzare il database per tenere traccia del blocco / sblocco degli oggetti


8

Ho l'obbligo di tenere traccia delle azioni di blocco / sblocco degli oggetti. Prima di qualsiasi azione eseguita su un oggetto (contratto, partner, ecc.), lockViene emesso un evento. Al termine dell'azione, genera l' unlockevento.

Voglio ottenere quegli oggetti che sono bloccati ma non ancora sbloccati. L'obiettivo è rendere la query rapida ed evitare deadlock.

Di seguito è la tabella

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)

Uso la query seguente per obiettare oggetti non ancora sbloccati:

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)

Funziona correttamente e risulta a, be d.

Le mie domande sono: - La mia soluzione è sufficiente per evitare deadlock? C'è qualche problema che può verificarsi se ce ne sono molti INSERTdurante l'esecuzione della query? - Hai qualche altro (migliore) modo per risolverlo?

AGGIORNARE

Mi scuso per non aver inserito il contesto nella domanda. La progettazione del database sopra non è per la sostituzione del blocco del database.

Abbiamo un sistema esterno che lo chiamiamo dal nostro sistema. Richiede di chiamare locke unlockmetodo sui loro sistemi prima di ogni azione eseguita su un oggetto (potrebbe essere un contratto o un partner).

Di recente, abbiamo situazioni tali che il server si arresta in modo anomalo e dobbiamo riavviarlo. Sfortunatamente, i processi in esecuzione che già hanno chiamato locknon hanno avuto la possibilità di chiamare unlockper rilasciare gli oggetti, portando quindi a molti altri problemi quando il nostro sistema si ricollega a quello esterno.

Quindi vogliamo fornire la possibilità di tracciare ogni lockchiamata. Al riavvio del server, chiameremo unlockgli oggetti precedentemente bloccati.

Grazie a Remus Rusanu per aver sottolineato che la mia domanda sta usando un prototipo DDL. Questa è la prima volta che ho pubblicato una domanda su DBA e mi scuso per non aver letto le FAQ.

Grazie

Risposte:


11

L'obiettivo è rendere la query rapida ed evitare deadlock.

Questo è un obiettivo non realistico. I deadlock sono determinati dall'applicazione che acquisisce i blocchi ed è fuori controllo su come implementare il blocco. Il meglio che puoi sperare è di rilevare deadlock.

L'implementazione di blocchi come record è problematica. Le righe persistono e perderai blocchi in caso di arresto anomalo dell'applicazione, anche quando l'implementazione è perfetta . Esegui i blocchi con gli applock . Hanno una chiara semantica transazionale e i deadlock vengono rilevati dal motore.

Tuttavia, guardando a ciò che si sta cercando di raggiungere, è improbabile che avete bisogno di serrature a tutti . Stai descrivendo una coda (seleziona l'elemento successivo disponibile per l'elaborazione ed evita conflitti => coda, non blocco). Leggi Utilizzo delle tabelle come code .

Per quanto riguarda l'implementazione specifica (utilizzando una tabella che mantiene la cronologia del blocco), devo essere sincero: è un disastro. Innanzitutto il design della tabella è completamente inadeguato per l'uso previsto: si esegue una query per nome ma la tabella è un heap senza indici. Ha una colonna di identità senza motivo apparente. Puoi rispondere che è solo una tabella "pseudo-codice", ma questo è DBA.SE, non pubblichi qui DDL incompleto!

Ma soprattutto, l'implementazione non implementa il blocco! Non c'è nulla che impedisca a due utenti di "bloccare" due volte lo stesso oggetto. Il tuo "blocco" si basa interamente sul comportamento magico dei chiamanti. Anche una migliore applicazione scritta non potrebbe usare questo blocco, perché non c'è modo di controllare e acquisire il blocco atomicamente . Due utenti possono verificare, concludere che "a" è sbloccato e inserire contemporaneamente il ('a', 1)record. Per lo meno avresti bisogno di un vincolo unico. Il che ovviamente spezzerebbe la semantica di "contare i blocchi contro sblocchi per determinare lo stato".

Mi dispiace dirlo, ma questa è un'implementazione di grado F.

AGGIORNARE

Quindi vogliamo fornire la possibilità di tracciare ogni chiamata di blocco. Al riavvio del server, chiameremo lo sblocco sugli oggetti precedentemente bloccati.

Senza impegnarti in una transazione distribuita in due fasi con il sistema remoto, tutto ciò che puoi fare è uno "sforzo migliore", perché ci sono molte condizioni di competizione tra te che scrivi lo "sblocco" e in realtà chiama "sblocco" sul sistema di terze parti . Come migliore sforzo, ecco la mia raccomandazione:

  • crea una semplice tabella per tracciare i blocchi:

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

  • prima di chiamare lockinserire il lucchetto nella tabella e confermare .

  • dopo aver chiamato unlock elimina il lucchetto dalla tabella e esegui il commit
  • all'avvio del sistema guarda la tabella. qualsiasi riga è presente un blocco residuo di una corsa precedente e deve essere "sbloccato". Chiama unlockper ogni riga, quindi elimina la riga. Solo dopo che tutti i blocchi in sospeso sono stati "sbloccati" è possibile ripristinare la normale funzionalità dell'app.

Lo sto proponendo perché il tavolo rimarrà piccolo. In qualsiasi momento contiene solo blocchi attivi e correnti. Non crescerà fino alla nausea e causerà problemi in seguito a causa delle sue dimensioni. È banale vedere cosa è bloccato.

Naturalmente questa implementazione non offre una cronologia di audit di ciò che è stato bloccato da chi e quando. Se necessario, puoi aggiungerlo come tabella diversa in cui inserire solo gli eventi (blocco o sblocco) e non interrogarlo mai per capire i blocchi "orfani".

Devi comunque essere pronto con le chiamate a "sbloccare" per non riuscire durante l'avvio perché non puoi garantire che il tuo sistema non si sia arrestato in modo anomalo dopo la chiamata unlockma prima di eliminare la riga (in altre parole, il tuo tavolo e il sistema di terze parti si sono separati e hanno differenze versioni della verità). Anche in questo caso non si può evitare questo w / o transazioni distribuite e non avrei mai consigliare per DTC.


4

Questo ucciderà la concorrenza sulla tua applicazione. SQL Server ha già tutto il necessario per evitare di aggiornare le stesse righe contemporaneamente, quindi non è davvero necessario farlo.

I deadlock possono verificarsi per diversi motivi, quindi ti suggerisco di indagare su questi motivi prima di creare il tuo sistema di chiusura. È possibile acquisire grafici di deadlock dalla sessione XE di integrità del sistema e iniziare ad analizzarli.

Di solito i deadlock sono il risultato di prendere blocchi su oggetti in diversi ordini, quindi dovresti provare a prendere blocchi sempre nello stesso ordine. Esistono altri motivi per cui possono verificarsi deadlock, ma la guida generale è che le transazioni brevi mantengano i blocchi per un breve periodo di tempo, quindi ottimizzare le tue query con codice migliore, indici e strategie divide et impera sarà probabilmente il modo migliore per sbarazzarsi di deadlock.

Il tipo di deadlock di "Lettori che bloccano gli scrittori" può essere fortemente mitigato passando il database in Isolamento istantaneo con commit della lettura . Se l'applicazione è stata creata pensando al blocco pessimistico, è necessario rivedere e testare tutto con molta attenzione prima di attivare questa opzione sul database.

Nel caso in cui insisti per seguire il percorso del "sistema di chiusura personalizzato", usa sempre qualcosa che garantisca il rilascio di blocchi nel caso in cui qualcosa con l'app vada storto. Potresti voler esaminare la stored procedure sp_getapplock integrata , che fa qualcosa di simile a quello che sembra essere dopo.

AGGIORNAMENTO: dopo aver letto la domanda modificata, questo è un modo alternativo per esprimere la stessa query:

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

Funzionerà se gli oggetti possono essere bloccati solo una volta, il che sembra comunque essere l'intero punto del sistema di bloccaggio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.