Perché la subquery usa il parallelismo e join no?


16

Perché il server SQL utilizza il parallelismo quando si esegue questa query che utilizza una sottoquery ma non quando si utilizza un join? La versione di join viene eseguita in serie e richiede circa 30 volte di più per essere completata.

Partecipa alla versione: ~ 30 secondi

inserisci qui la descrizione dell'immagine

Versione della subquery: <1 secondo

inserisci qui la descrizione dell'immagine

EDIT: versioni XML del piano di query:

Versione JOIN

Versione SUBQUERY

Risposte:


12

Come già indicato nei commenti sembra che tu debba aggiornare le tue statistiche.

Il numero stimato di righe che escono dall'unione tra locationed testrunsè enormemente diverso tra i due piani.

Unire le stime del piano: 1

Piano 1

Stime del piano di query secondarie: 8.748

inserisci qui la descrizione dell'immagine

Il numero effettivo di righe che escono dal join è 14.276.

Ovviamente non ha assolutamente senso intuitivo che la versione di join dovrebbe stimare che dovrebbero provenire 3 righe locatione produrre una singola riga unita mentre la query secondaria stima che una sola di quelle righe produrrà 8.748 dallo stesso join, ma ciò nonostante sono stato in grado per riprodurre questo.

Ciò sembra accadere se non viene incrociato l'istogramma quando vengono create le statistiche. La versione di join assume una sola riga. E la ricerca di uguaglianza singola della query secondaria assume le stesse righe stimate di una ricerca di uguaglianza rispetto a una variabile sconosciuta.

La cardinalità dei testrun è 26244. Supponendo che sia popolato con tre ID di posizione distinti, la query seguente stima che 8,748verranno restituite le righe ( 26244/3)

declare @i int

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = @i

Dato che la tabella locationscontiene solo 3 righe, è facile (se non assumiamo chiavi esterne) inventare una situazione in cui vengono create le statistiche e quindi i dati vengono alterati in modo da avere un effetto drammatico sul numero effettivo di righe restituite ma non è sufficiente per attivare l'aggiornamento automatico delle statistiche e ricompilare la soglia.

Man mano che SQL Server rileva il numero di righe che escono da quel join, tutte le stime delle altre righe nel piano di join vengono erroneamente sottovalutate. Oltre a significare che si ottiene un piano seriale, la query ottiene anche una concessione di memoria insufficiente e i tipi e i join hash si riversano a tempdb.

Di seguito è riportato uno scenario possibile che riproduce le righe effettive rispetto a quelle visualizzate nel piano.

CREATE TABLE location
  (
     id       INT CONSTRAINT locationpk PRIMARY KEY,
     location VARCHAR(MAX) /*From the separate filter think you are using max?*/
  )

/*Temporary ids these will be updated later*/
INSERT INTO location
VALUES      (101, 'Coventry'),
            (102, 'Nottingham'),
            (103, 'Derby')

CREATE TABLE testruns
  (
     location_id INT
  )

CREATE CLUSTERED INDEX IX ON testruns(location_id)

/*Add in 26244 rows of data split over three ids*/
INSERT INTO testruns
SELECT TOP (5984) 1
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (5984) 2
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (14276) 3
FROM   master..spt_values v1, master..spt_values v2

/*Create statistics. The location_id histograms don't intersect at all*/
UPDATE STATISTICS location(locationpk) WITH FULLSCAN;    
UPDATE STATISTICS testruns(IX) WITH FULLSCAN;

/* UPDATE location.id. Three row update is below recompile threshold*/
UPDATE location
SET    id = id - 100

Quindi eseguire le seguenti query fornisce la stessa discrepanza stimata rispetto all'effettiva

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = (SELECT id
                         FROM   location
                         WHERE  location = 'Derby')

SELECT *
FROM   testruns AS tr
       JOIN location loc
         ON tr.location_id = loc.id
WHERE  loc.location = ( 'Derby' ) 

Se viene aggiunto un vincolo univoco sulla posizione, diventa evidente che "=" restituirà esattamente una riga. Quindi nel tuo esempio i piani di query diventano identici (scansioni -> ricerche): modifica la posizione della tabella aggiungi il vincolo U_Location_Location univoco non cluster (Posizione);
crokusek,

@crokusek sì. Ho capito cosa intendevi in ​​seguito e ho eliminato il mio commento! Ciò aumenta il numero stimato di righe per la versione di join allo stesso modo della sottoquery? Al momento non sei su PC per testarlo?
Martin Smith,

@crokusek Yep. assomiglia alle stesse righe stimate dal join della query secondaria in quel caso singleton.
Martin Smith,

Sì. Piano di query identico, entrambe stime 8748, entrambe effettive 14276. A proposito, ho pensato che pre-calcolare la posizione avrei risolto quella differenza, ma non è così.
crokusek,

1
@crokusek - Aggiungerò anche il vincolo univoco alla posizione e ad altri luoghi simili all'interno del mio DB. Devo ammettere che non mi ero reso conto che influiva sull'ottimizzazione delle query. Ho pensato che fosse solo per garantire l'integrità dei dati. Grazie per il tuo contributo a questa domanda.
Chris L,
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.