Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come ottenere il numero successivo in una sequenza

Se non gestisci una tabella contatore, ci sono due opzioni. All'interno di una transazione, seleziona prima il MAX(seq_id) con uno dei seguenti suggerimenti per la tabella:

  1. WITH(TABLOCKX, HOLDLOCK)
  2. WITH(ROWLOCK, XLOCK, HOLDLOCK)

TABLOCKX + HOLDLOCK è un po' eccessivo. Blocca le normali istruzioni select, che possono essere considerate pesanti anche se la transazione è piccola.

Un ROWLOCK, XLOCK, HOLDLOCK il suggerimento per la tabella è probabilmente un'idea migliore (ma:leggi l'alternativa con una tabella contatore più avanti). Il vantaggio è che non blocca le istruzioni select regolari, cioè quando le istruzioni select non compaiono in un SERIALIZABLE transazione o quando le istruzioni select non forniscono gli stessi suggerimenti per la tabella. Usando ROWLOCK, XLOCK, HOLDLOCK bloccherà comunque le istruzioni di inserimento.

Ovviamente devi essere sicuro che nessun'altra parte del tuo programma selezioni MAX(seq_id) senza questi suggerimenti per la tabella (o al di fuori di un SERIALIZABLE transazione) e quindi utilizzare questo valore per inserire le righe.

Si noti che, a seconda del numero di righe bloccate in questo modo, è possibile che SQL Server esegua l'escalation del blocco in un blocco di tabella. Ulteriori informazioni sull'escalation dei blocchi qui .

La procedura di inserimento utilizzando WITH(ROWLOCK, XLOCK, HOLDLOCK) avrebbe il seguente aspetto:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @max_seq IS NULL SET @max_seq=0;
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Un'idea alternativa e probabilmente migliore è quella di avere un contatore tavolo e fornire questi suggerimenti per la tabella sul bancone. Questa tabella sarebbe simile alla seguente:

CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);

Dovresti quindi modificare la procedura di inserimento come segue:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @new_seq IS NULL 
        BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
    ELSE
        BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Il vantaggio è che vengono utilizzati meno blocchi di riga (ad esempio uno per modello in dbo.counter_seq ) e l'escalation del blocco non può bloccare l'intero dbo.table_seq tabella bloccando così le istruzioni select.

Puoi testare tutto questo e vedere tu stesso gli effetti, inserendo un WAITFOR DELAY '00:01:00' dopo aver selezionato la sequenza da counter_seq e giocherellare con i tavoli in una seconda scheda SSMS.

PS1:utilizzo di ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID) non è un buon modo. Se le righe vengono eliminate/aggiunte o vengono modificati gli ID, la sequenza cambierà (considerare gli ID fattura che non dovrebbero mai cambiare). Anche in termini di prestazioni, dover determinare i numeri di riga di tutte le righe precedenti quando si recupera una singola riga è una cattiva idea.

PS2:non userei mai risorse esterne per fornire il blocco, quando SQL Server fornisce già il blocco tramite livelli di isolamento o suggerimenti di tabella a grana fine.