Non ci sarebbe un problema se la variabile della tabella contenesse un solo valore. Con più righe, esiste una nuova possibilità di deadlock. Supponiamo che due processi simultanei (A e B) vengano eseguiti con variabili di tabella contenenti (1, 2) e (2, 1) per la stessa azienda.
Il processo A legge la destinazione, non trova alcuna riga e inserisce il valore "1". Contiene un blocco riga esclusivo sul valore "1". Il processo B legge la destinazione, non trova alcuna riga e inserisce il valore "2". Contiene un blocco riga esclusivo sul valore "2".
Ora il processo A deve elaborare la riga 2 e il processo B deve elaborare la riga 1. Nessuno dei due processi può avanzare perché richiede un blocco incompatibile con il blocco esclusivo detenuto dall'altro processo.
Per evitare deadlock con più righe, le righe devono essere elaborate (e le tabelle accessibili) nello stesso ordine ogni volta . La variabile di tabella nel piano di esecuzione mostrato nella domanda è un heap, quindi le righe non hanno un ordine intrinseco (è molto probabile che vengano lette in ordine di inserimento, sebbene ciò non sia garantito):
La mancanza di un ordine di elaborazione delle righe coerente porta direttamente all'opportunità di deadlock. Una seconda considerazione è che la mancanza di una garanzia di unicità fondamentale significa che una bobina da tavolo è necessaria per fornire una corretta protezione di Halloween. Lo spool è uno spool desideroso, il che significa che tutte le righe vengono scritte su un tavolo di lavoro tempdb prima di essere rilette e riprodotte per l'operatore Inserisci.
Ridefinire la TYPE
variabile della tabella per includere un cluster PRIMARY KEY
:
DROP TYPE dbo.CoUserData;
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL PRIMARY KEY CLUSTERED,
MyValue integer NOT NULL
);
Il piano di esecuzione ora mostra una scansione dell'indice cluster e la garanzia di unicità significa che l'ottimizzatore è in grado di rimuovere in sicurezza lo spool della tabella:
Nei test con 5000 iterazioni MERGE
dell'istruzione su 128 thread, non si sono verificati deadlock con la variabile di tabella cluster. Dovrei sottolineare che questo è solo sulla base dell'osservazione; la variabile di tabella raggruppata potrebbe anche ( tecnicamente ) produrre le sue righe in una varietà di ordini, ma le possibilità di un ordine coerente sono notevolmente migliorate. Il comportamento osservato dovrebbe essere nuovamente testato per ogni nuovo aggiornamento cumulativo, service pack o nuova versione di SQL Server, ovviamente.
Nel caso in cui la definizione della variabile di tabella non possa essere modificata, esiste un'altra alternativa:
MERGE dbo.CompanyUser AS R
USING
(SELECT DISTINCT MyKey, MyValue FROM @DataTable) AS NewData ON
R.CompanyId = @CompanyID
AND R.UserID = @UserID
AND R.MyKey = NewData.MyKey
WHEN NOT MATCHED THEN
INSERT
(CompanyID, UserID, MyKey, MyValue)
VALUES
(@CompanyID, @UserID, NewData.MyKey, NewData.MyValue)
OPTION (ORDER GROUP);
Ciò consente anche di eliminare lo spool (e la coerenza dell'ordine delle righe) al costo di introdurre un ordinamento esplicito:
Questo piano inoltre non ha prodotto deadlock utilizzando lo stesso test. Script di riproduzione di seguito:
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL /* PRIMARY KEY */,
MyValue integer NOT NULL
);
GO
CREATE TABLE dbo.Company
(
CompanyID integer NOT NULL
CONSTRAINT PK_Company
PRIMARY KEY (CompanyID)
);
GO
CREATE TABLE dbo.CompanyUser
(
CompanyID integer NOT NULL,
UserID integer NOT NULL,
MyKey integer NOT NULL,
MyValue integer NOT NULL
CONSTRAINT PK_CompanyUser
PRIMARY KEY CLUSTERED
(CompanyID, UserID, MyKey),
FOREIGN KEY (CompanyID)
REFERENCES dbo.Company (CompanyID),
);
GO
CREATE NONCLUSTERED INDEX nc1
ON dbo.CompanyUser (CompanyID, UserID);
GO
INSERT dbo.Company (CompanyID) VALUES (1);
GO
DECLARE
@DataTable AS dbo.CoUserData,
@CompanyID integer = 1,
@UserID integer = 1;
INSERT @DataTable
SELECT TOP (10)
V.MyKey,
V.MyValue
FROM
(
VALUES
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9)
) AS V (MyKey, MyValue)
ORDER BY NEWID();
BEGIN TRANSACTION;
-- Test MERGE statement here
ROLLBACK TRANSACTION;