Perché la modifica dell'ordine delle colonne di join dichiarate introduce un ordinamento?


40

Ho due tabelle con colonne chiave identificate, digitate e indicizzate. Uno di questi ha un indice cluster univoco , l'altro ha un indice non univoco .

L'impostazione del test

Script di installazione, incluse alcune statistiche realistiche:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

La riproduzione

Quando unisco queste due tabelle sulle loro chiavi di clustering, mi aspetto un join MERGE uno-a-molti, in questo modo:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

Questo è il piano di query che desidero:

Questo è quello che voglio.

(Non importa gli avvertimenti, hanno a che fare con le statistiche false.)

Tuttavia, se cambio l'ordine delle colonne attorno al join, in questo modo:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... questo succede:

Il piano di query dopo aver modificato l'ordine delle colonne dichiarato nel join.

L'operatore di ordinamento sembra ordinare i flussi secondo l'ordine dichiarato del join, vale a dire c, a, b, d, e, f, g, hche aggiunge un'operazione di blocco al mio piano di query.

Cose che ho visto

  • Ho provato a cambiare le colonne in NOT NULL, stessi risultati.
  • La tabella originale è stata creata con ANSI_PADDING OFF, ma la sua creazione ANSI_PADDING ONnon influisce su questo piano.
  • Ho provato un INNER JOINinvece di LEFT JOIN, nessun cambiamento.
  • L'ho scoperto su una SP2 Enterprise 2014, ho creato una riproduzione su uno sviluppatore del 2017 (attuale CU).
  • La rimozione della clausola WHERE sulla colonna dell'indice principale genera un buon piano, ma influisce in qualche modo sui risultati .. :)

Finalmente arriviamo alla domanda

  • È intenzionale?
  • Posso eliminare l'ordinamento senza modificare la query (che è il codice del fornitore, quindi preferirei davvero ...). Posso cambiare la tabella e gli indici.

Risposte:


28

È intenzionale?

È di progettazione, sì. La migliore fonte pubblica per questa affermazione è stata purtroppo persa quando Microsoft ha ritirato il sito di feedback di Connect, cancellando molti commenti utili dagli sviluppatori del team di SQL Server.

Comunque, l'attuale progetto di ottimizzazione non cerca attivamente di evitare di per sé tipi inutili . Ciò si riscontra più spesso con funzioni di finestratura e simili, ma può anche essere visto con altri operatori sensibili all'ordinamento, e in particolare all'ordinamento conservato tra gli operatori.

Tuttavia, l'ottimizzatore è abbastanza buono (in molti casi) per evitare lo smistamento non necessario, ma questo risultato si verifica normalmente per ragioni diverse dal provare in modo aggressivo combinazioni di ordinamento diverse. In tal senso, non si tratta tanto di "spazio di ricerca" quanto delle interazioni complesse tra le funzioni di ottimizzazione ortogonale che hanno dimostrato di aumentare la qualità generale del piano a costi accettabili.

Ad esempio, l'ordinamento può spesso essere evitato semplicemente abbinando un requisito di ordinazione (ad esempio di livello superiore ORDER BY) a un indice esistente. Fondamentalmente nel tuo caso ciò potrebbe significare aggiungere, ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;ma questa è una semplificazione eccessiva (e inaccettabile perché non vuoi cambiare la query).

Più in generale, ciascun gruppo di promemoria può essere associato alle proprietà richieste o desiderate, che possono includere l'ordinamento degli input. Quando non vi è alcuna ragione ovvia per far rispettare un determinato ordine (ad esempio per soddisfare un ORDER BY, o per garantire risultati corretti da un operatore fisico sensibile all'ordine), vi è un elemento di "fortuna". Ho scritto di più sui dettagli di ciò per quanto riguarda l'unione dei join (in modalità unione o join) in Evitare le specie con Concatenazione di join Merge . Gran parte di questo va oltre la superficie supportata del prodotto, quindi trattalo come informativo e soggetto a modifiche.

Nel tuo caso particolare, sì, puoi regolare l'indicizzazione come jadarnel27 suggerisce di evitare le specie; anche se ci sono poche ragioni per preferire un join di unione qui. Puoi anche suggerire una scelta tra un hash o un loop fisico con l' OPTION(HASH JOIN, LOOP JOIN)utilizzo di una guida di piano senza modificare la query, a seconda della tua conoscenza dei dati, e il compromesso tra le prestazioni migliori, peggiori e nel caso medio.

Infine, per curiosità, nota che i tipi possono essere evitati con un semplice ORDER BY l.b, al costo di un join molti-molti potenzialmente meno efficiente unirsi da bsolo, con un residuo complesso. Lo menziono principalmente come illustrazione dell'interazione tra le funzioni di ottimizzazione che ho menzionato in precedenza e il modo in cui i requisiti di livello superiore possono propagarsi.


19

Posso eliminare l'ordinamento senza modificare la query (che è il codice del fornitore, quindi preferirei davvero ...). Posso cambiare la tabella e gli indici.

Se è possibile modificare gli indici, la modifica dell'ordine dell'indice in modo #rightche corrisponda all'ordine dei filtri nel join rimuove l'ordinamento (per me):

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

Sorprendentemente (almeno per me), questo non porta a nessuna query che finisce con un ordinamento.

È intenzionale?

Guardando l'output di alcune strane flag di traccia , c'è una differenza interessante nella struttura finale del Memo:

screenshot della struttura del memo finale per ogni query

Come puoi vedere nel "Gruppo radice" in alto, entrambe le query hanno la possibilità di utilizzare un Unisci unione come operazione fisica principale per eseguire questa query.

Buona domanda

L'unione senza ordinamento è guidata dall'opzione 1 del gruppo 29 e dall'opzione 1 del gruppo 31 (ciascuna delle quali è una scansione di intervallo sugli indici interessati). È filtrato dal gruppo 27 (non mostrato), che è la serie di operazioni logiche di confronto che filtrano il join.

Query errata

Quello con l'ordinamento è guidato dalle (nuove) opzioni 3 che ciascuno di questi due gruppi (29 e 31) ha. L'opzione 3 esegue un ordinamento fisico sui risultati delle scansioni dell'intervallo menzionate in precedenza (opzione 1 di ciascuno di tali gruppi).

Perché?

Per qualche motivo, l'opzione per utilizzare 29.1 e 31.1 direttamente come origini per l'unione unione non è nemmeno disponibile per l'ottimizzatore nella seconda query. Altrimenti, penso che sarebbe elencato sotto il gruppo radice tra le altre opzioni. Se fosse disponibile, sceglierebbe sicuramente quelli tra le operazioni di ordinamento enormemente più costose.

Posso solo concludere che:

  • questo è un bug (o più probabilmente una limitazione) nell'algoritmo di ricerca dell'ottimizzatore
    • la modifica degli indici e dei join per avere solo 5 chiavi rimuove l'ordinamento per la seconda query (6, 7 e 8 chiavi hanno tutte l'ordinamento).
    • Ciò implica che lo spazio di ricerca con 8 chiavi è così grande che l'ottimizzatore non ha il tempo di identificare la soluzione non ordinata come un'opzione praticabile prima che si concluda presto con la ragione "trovato piano abbastanza buono"
    • mi sembra un po 'buggy che l'ordine delle condizioni di join influenzi così tanto il processo di ricerca dell'ottimizzatore, ma in realtà è un po' sopra la mia testa
  • l'ordinamento è necessario per garantire la correttezza dei risultati
    • questo sembra improbabile, poiché la query può essere eseguita senza l'ordinamento quando ci sono meno chiavi o le chiavi sono specificate in un ordine diverso

Spero che qualcuno possa venire e spiegare perché è richiesto l'ordinamento, ma ho pensato che la differenza nell'edificio Memo fosse abbastanza interessante da pubblicare come risposta.


1
Credo che il tuo commento sullo spazio di ricerca sia effettivamente il caso qui. per utilizzare solo gli indici, l'ottimizzatore deve verificare che siano sufficienti per le condizioni, oltre 5 tasti ci sono troppe possibilità da verificare prima che debba ricadere. Sarei curioso, se fossero enumerate tutte le combinazioni di ordini della query, quanti ottimizzatori avrebbero avuto successo su vs
fallback

E sì, l'incoerenza sembra un po 'buggy, ma è probabilmente totalmente dipendente dall'algoritmo utilizzato per verificare che gli indici siano sufficienti. Se tutte le combinazioni fossero state testate, probabilmente saresti in grado di vedere lo schema nei risultati e determinare quale algoritmo viene utilizzato. Scommetto che è scritto per funzionare in modo ottimale per i casi d'uso più tipici. Potrebbe esistere un'alternativa che sarebbe in grado di trovare la soluzione a 8 tasti in modo affidabile entro il limite di tempo, ma è più lenta della soluzione attuale quando ci sono meno di 3-4 tasti.
Mr.Mindor,
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.