Record duplicati restituiti dalla tabella senza duplicati


8

Ho una procedura memorizzata che richiede una tabella di coda occupata che viene utilizzata per distribuire il lavoro nel nostro sistema. La tabella in questione ha una chiave primaria su WorkID e nessun duplicato.

Una versione semplificata della query è:

INSERT INTO #TempWorkIDs (WorkID)
SELECT
        W.WorkID

    FROM
        dbo.WorkTable W

    WHERE
        (@bool_param = 0 AND
        ((W.InProgress = 0
         AND ISNULL(W.UserID, -1) != @userid_param
         AND (@bool_filtered = 0
              OR W.TypeID IN (SELECT TypeID FROM #Types AS t)))
         OR 
         (@bool_param = 1
          AND W.InProgress = 1
          AND W.UserID != @userid_param)
        OR
        (@Auto_Param = 0
         AND W.UserID = @userid_param)))
         OR
         (@bool_param = 1 AND W.UserID = @userid_param)
    OPTION
        (RECOMPILE)

La #Typestabella viene popolata in precedenza nella procedura.

Come ho detto, WorkTableè occupato e, a volte, mentre questa query è in esecuzione, SOSPETTO uno dei record si sposta da un set di filtri WHEREall'altro. In particolare, ciò accade quando qualcuno inizia a lavorare su un elemento e le W.InProgressmodifiche da 0 a 1. Quando ciò accade, ottengo una violazione della chiave duplicata quando provo ad aggiungere una chiave primaria alla tabella temporanea in cui viene inserita questa query.

Ho confermato nel piano di query generato quando si verifica l'errore che non c'è parallelismo, il livello di isolamento è READ COMMITTEDe non ci sono record duplicati nella tabella di origine. Puoi anche vedere che non ci sono JOINaltri modi per ottenere prodotti cartesiani qui.

Questo è il piano di query anonimizzato:

inserisci qui la descrizione dell'immagine

La domanda è: cosa sta causando i duplicati e come posso farlo arrestare?

Penso che READ COMMITTEDdovrebbe funzionare qui, ho bisogno di blocco. Sono quasi sicuro che i duplicati si verifichino quando il InProgressbit su un record cambia mentre sto interrogando. Lo so perché la tabella memorizza l'ora di tale modifica ed è entro millisecondi di quando eseguo una query e ottengo l'errore.

Risposte:


9

Ci sono alcuni scenari difficili che possono far sì che la stessa riga venga letta due volte da un indice, anche sotto il READ COMMITTEDlivello di isolamento .

La query non è idonea per una scansione dell'ordine di allocazione, quindi il motore di archiviazione leggerà i dati dalla tabella nell'ordine della chiave cluster.

Per la tua tabella, hai InProgressla prima colonna della chiave in cluster. È probabile che si ottengano blocchi di riga o di pagina durante la scansione della tabella. Se leggi una riga vicino all'inizio della scansione, rilascia il blocco su di essa, quella riga viene aggiornata in modo tale da InProgresscambiare da 0 a 1 e quindi la riga viene letta di nuovo in una pagina diversa, quindi potresti vedere WorkIDvalori duplicati dalla tua query .

Esistono molte soluzioni alternative. È possibile inserire in un heap e rimuovere semplicemente i valori duplicati. È possibile aggiungere DISTINCTa alla query. È inoltre possibile abilitare un livello di isolamento del controllo delle versioni delle righe per fornire una visualizzazione stabile dello stato di commit del database, sia all'inizio della transazione ( isolamento dello snapshot ), sia all'inizio dell'istruzione ( leggere l'isolamento dello snapshot di commit ).

Forse è opportuno aggiungere suggerimenti di blocco o modificare la struttura della tabella. Per una soluzione piuttosto divertente (probabilmente non adatta alla produzione), potresti provare a leggere l'indice al contrario. Questo può essere fatto con un superfluo TOPinsieme a un ORDER BY. Di seguito è una demo molto semplice per illustrare il punto:

CREATE TABLE #WorkTable (
    InProgress TINYINT NOT NULL,
    WorkID INT NOT NULL
    , PRIMARY KEY (InProgress, WorkID)
);

INSERT INTO #WorkTable WITH (TABLOCK)
SELECT (RN - 1) / 5000, RN
FROM
(
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t
OPTION (MAXDOP 1);

La query seguente ha la proprietà Ordered: false ma continuerà a leggere i dati nell'ordine di chiavi cluster:

SELECT WorkId
FROM #WorkTable;

Tuttavia, la query seguente leggerà i dati in ordine di cluster inverso:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;

Possiamo vedere questo guardando le proprietà di scansione:

scansione all'indietro

Per la tua tabella, ciò significa che se una riga viene aggiornata in modo tale InProgressda passare da 0 a 1, sarà molto meno probabile che venga visualizzata due volte. Potrebbe non apparire affatto che potrebbe essere un problema diverso.

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.