Sono abbastanza sicuro che le definizioni della tabella sono vicine a questo:
CREATE TABLE dbo.households
(
tempId integer NOT NULL,
n integer NOT NULL,
HHID integer IDENTITY NOT NULL,
CONSTRAINT [UQ dbo.households HHID]
UNIQUE NONCLUSTERED (HHID),
CONSTRAINT [PK dbo.households tempId, n]
PRIMARY KEY CLUSTERED (tempId, n)
);
CREATE TABLE dbo.persons
(
tempId integer NOT NULL,
sporder integer NOT NULL,
n integer NOT NULL,
PERID integer IDENTITY NOT NULL,
HHID integer NOT NULL,
CONSTRAINT [UQ dbo.persons HHID]
UNIQUE NONCLUSTERED (PERID),
CONSTRAINT [PK dbo.persons tempId, n, sporder]
PRIMARY KEY CLUSTERED (tempId, n, sporder)
);
Non ho statistiche per queste tabelle o i tuoi dati, ma quanto segue imposterà almeno la cardinalità della tabella corretta (i conteggi delle pagine sono un'ipotesi):
UPDATE STATISTICS dbo.persons
WITH
ROWCOUNT = 5239842,
PAGECOUNT = 100000;
UPDATE STATISTICS dbo.households
WITH
ROWCOUNT = 1928783,
PAGECOUNT = 25000;
Analisi del piano di query
La query che hai ora è:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n;
Questo genera il piano piuttosto inefficiente:
I problemi principali in questo piano sono l'hash join e l'ordinamento. Entrambi richiedono una concessione di memoria (il join hash deve creare una tabella hash e l'ordinamento ha bisogno di spazio per memorizzare le righe mentre l'ordinamento avanza). Plan Explorer mostra che a questa query sono stati concessi 765 MB:
Questa è molta memoria del server da dedicare a una query! Più precisamente, questa concessione di memoria viene risolta prima che inizi l'esecuzione in base al conteggio delle righe e alle stime delle dimensioni.
Se la memoria risulta insufficiente al momento dell'esecuzione, almeno alcuni dati per l'hash e / o l'ordinamento verranno scritti sul disco tempdb fisico . Questo è noto come "fuoriuscita" e può essere un'operazione molto lenta. È possibile tracciare questi sversamenti (in SQL Server 2008) utilizzando il profiler eventi Avvertenze Hash e Ordina avvertenze .
La stima per l'input di compilazione della tabella hash è molto buona:
La stima per l'input di ordinamento è meno accurata:
Dovresti usare Profiler per verificare, ma sospetto che l'ordinamento si riverserà su tempdb in questo caso. È anche possibile che la tabella hash si rovesci, ma è meno chiara.
Si noti che la memoria riservata per questa query viene suddivisa tra la tabella hash e l'ordinamento, poiché vengono eseguiti contemporaneamente. La proprietà del piano Frazioni di memoria mostra la quantità relativa della concessione di memoria che si prevede sarà utilizzata da ciascuna operazione.
Perché ordinare e hash?
L'ordinamento viene introdotto da Query Optimizer per garantire che le righe arrivino all'operatore Aggiornamento indice cluster in ordine di chiavi cluster. Ciò promuove l'accesso sequenziale alla tabella, che è spesso molto più efficiente dell'accesso casuale.
L'hash join è una scelta meno ovvia, perché i suoi input hanno dimensioni simili (comunque a una prima approssimazione). L'hash join è il migliore in cui un input (quello che crea la tabella hash) è relativamente piccolo.
In questo caso, il modello di costing dell'ottimizzatore determina che l'hash join è la più economica delle tre opzioni (hash, merge, loop nidificati).
Miglioramento delle prestazioni
Il modello di costo non sempre funziona correttamente. Tende a sovrastimare il costo dell'unione di unione parallela, soprattutto all'aumentare del numero di thread. Possiamo forzare un join di unione con un suggerimento per la query:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (MERGE JOIN);
Questo produce un piano che non richiede tanta memoria (poiché l'unione di join non ha bisogno di una tabella hash):
L'ordinamento problematico è ancora presente, poiché l'unione unione mantiene solo l'ordine delle sue chiavi di unione (tempId, n) ma le chiavi del cluster sono (tempId, n, sporder). È possibile che il piano di unione unificata non sia migliore del piano di join hash.
Unisci loop annidati
Possiamo anche provare a unire loop nidificati:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (LOOP JOIN);
Il piano per questa query è:
Questo piano di query è considerato il peggiore dal modello di costing dell'ottimizzatore, ma ha alcune caratteristiche molto desiderabili. Innanzitutto, l'unione di cicli nidificati non richiede una concessione di memoria. In secondo luogo, può preservare l'ordine delle chiavi dalla Persons
tabella in modo che non sia necessario un ordinamento esplicito. Potresti scoprire che questo piano funziona relativamente bene, forse anche abbastanza bene.
Cicli annidati paralleli
Il grande svantaggio con il piano di cicli nidificati è che viene eseguito su un singolo thread. È probabile che questa query tragga vantaggio dal parallelismo, ma l'ottimizzatore decide che qui non c'è alcun vantaggio nel farlo. Anche questo non è necessariamente corretto. Sfortunatamente, non esiste un suggerimento di query integrato per ottenere un piano parallelo, ma esiste un modo non documentato:
UPDATE t1
SET t1.HHID = t2.HHID
FROM dbo.persons AS t1
INNER JOIN dbo.households AS t2
ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);
L'abilitazione del flag di traccia 8649 con il QUERYTRACEON
suggerimento produce questo piano:
Ora abbiamo un piano che evita l'ordinamento, non richiede memoria aggiuntiva per il join e utilizza il parallelismo in modo efficace. Dovresti trovare che questa query funziona molto meglio delle alternative.
Maggiori informazioni sul parallelismo nel mio articolo Forcing a Parallel Query Execution Plan :