Database
 sql >> Database >  >> RDS >> Database

Obiettivi di fila, parte 3:Anti join

Questo post fa parte di una serie di articoli sugli obiettivi di fila. Puoi trovare le altre parti qui:

  • Parte 1:definizione e identificazione degli obiettivi di riga
  • Parte 2:Semi join

Questa parte spiega quando e perché l'ottimizzatore introduce un obiettivo di riga per un anti join.

Introduzione

Un anti join è anche noto come anti semi join. Restituisce ogni riga dall'input di join A per la quale nessuna corrispondenza si trova all'ingresso B.

Per un anti join:

  • L'ottimizzatore potrebbe aggiungi un obiettivo della riga interna a un applica (unire i loop nidificati correlati) solo anti join .
  • Un obiettivo di riga non è stato aggiunto per cicli nidificati non correlati anti join, hash anti join o merge anti join.
  • Come sempre, qualsiasi obiettivo di riga viene solo aggiunto se è inferiore alla stima senza un obiettivo di riga applicato.
  • Lato interno ridondante TOP clausole e DISTINCT/GROUP BY le operazioni possono essere semplificate.

Espandendo il primo punto elenco sopra, la differenza principale tra applicare semi join e applicare obiettivi di riga anti join è:

  • Un applica semi join presenta sempre un obiettivo in linea (purché sia ​​inferiore alla stima senza l'obiettivo).
  • Un applica anti join può presentare un obiettivo in linea , ma solo se un anti join logico viene trasformato in un'applicazione durante l'ottimizzazione basata sui costi .

Mi scuso per il fatto che queste regole non siano più semplici, ma non le ho fatte io. Si spera che alcune discussioni ed esempi rendano tutto più chiaro.

Nessun obiettivo di fila anti join per impostazione predefinita

L'ottimizzatore presuppone che le persone scrivano un semi join (indirettamente ad es. usando EXISTS ) con l'aspettativa che la riga cercata verrà trovata . Un'applicazione semi join è impostato un obiettivo di riga dall'ottimizzatore per trovare rapidamente la riga corrispondente prevista.

Per anti join (espresso ad es. usando NOT EXISTS ) il presupposto dell'ottimizzatore è che una riga corrispondente non verrà trovata . Un'applicazione anti join l'obiettivo di riga non è impostato dall'ottimizzatore, perché prevede di dover controllare tutte le righe per confermare che non vi siano corrispondenze.

Se risulta essere una riga corrispondente, l'applicazione anti join potrebbe richiedere più tempo per individuare questa riga rispetto a se fosse stato utilizzato un obiettivo di riga. Tuttavia, l'anti join terminerà comunque la sua ricerca non appena viene rilevata la corrispondenza (inaspettata).

Applica le condizioni dell'obiettivo Anti Join Row

SQL Server non ci fornisce un modo per scrivere direttamente un anti join, quindi dobbiamo usare soluzioni alternative come NOT EXISTS , NOT IN/ANY/SOME o EXCEPT . Ciascuno di questi moduli risulta in una rappresentazione di sottoquery nell'albero analizzato all'inizio della compilazione della query. Questa sottoquery viene sempre srotolata in un'applicazione e quindi trasformata in un anti join logico ove possibile (i dettagli sono gli stessi del semi join discusso nella parte 2). Tutto questo accade prima ancora che venga preso in considerazione un piano banale.

Affinché un anti join ottenga un goal di fila, deve entrare ottimizzazione basata sui costi come anti join logico (il che significa che la trasformazione da una richiesta di cui sopra deve aver avuto successo). Quindi, l'ottimizzatore basato sui costi deve scegliere di implementare l'anti join logico come applicazione . Affinché ciò avvenga, l'ottimizzatore deve prima scegliere di esplorare l'opzione applica; quindi deve selezionare che è l'opzione più economica (per quella parte del piano).

Un obiettivo di riga anti join è impostato da una qualsiasi delle regole di ottimizzazione basate sui costi che possono trasformare un join in un'applicazione. Un anti join che entra ottimizzazione basata sui costi come richiesta (perché la trasformazione in anti join logico non è riuscita) non avere un obiettivo di riga applicato.

L'ottimizzatore basato sui costi esplorerà e selezionerà l'opzione di unione per l'applicazione solo se esiste un modo efficiente per individuare le righe laterali interne corrispondenti (ad esempio utilizzando un indice). L'ottimizzatore non esplorerà l'opzione se il sottoalbero del lato interno join non ha elementi utili per il predicato apply a cui "agganciarsi". Potrebbe essere un indice, un indice temporaneo (tramite uno spool di indice desideroso) o un'altra chiave logica. L'aggiunta dell'obiettivo di riga aiuta l'ottimizzatore a valutare il costo dell'opzione di adesione per l'applicazione, dato che è necessario individuare un massimo di una riga.

Si noti che un'applicazione anti join potrebbe apparire in un piano di esecuzione senza un obiettivo di riga. Ciò si verifica quando la trasformazione iniziale da apply a join ha esito negativo, come è relativamente comune. Quando ciò accade, l'anti join inizia a funzionare nell'ottimizzatore basato sui costi come applicazione, quindi non viene mai aggiunto un obiettivo di riga da una delle regole di join-to-applica.

Ovviamente un obiettivo di riga può essere introdotto anche sul lato interno di questa applicazione tramite un meccanismo diverso (non associato all'applicazione), ad esempio da un operatore Top separato.

Per riassumere:

  • Un anti join può ottenere un obiettivo di fila solo durante l'ottimizzazione basata sui costi (CBO).
  • Le regole che traducono un anti join in un'applicazione aggiungono un obiettivo di riga.
  • L'anti join deve entrare in CBO come join, non come application.
  • Per inserire CBO come join, le fasi precedenti devono essere in grado di riscrivere la sottoquery come join (tramite una fase di applicazione).
  • CBO esplora l'unione per applicare la trasformazione solo nei casi promettenti.

Esempio

È un po' più complicato dimostrare tutto questo per applicare l'anti join rispetto a quanto non fosse per l'applicazione semi join. Le ragioni di ciò saranno trattate nella parte 4.

Nel frattempo, ecco un esempio di AdventureWorks che mostra come si verifica un'applicazione anti join con obiettivo di riga, utilizzando gli stessi flag di traccia non documentati come per il semi join. Viene aggiunto il flag di traccia 8608 per mostrare la struttura iniziale del memo all'inizio dell'ottimizzazione basata sui costi.

SELECT P.ProductID 
FROM Production.Product AS P
WHERE 
    NOT EXISTS 
    (
        SELECT 1
        FROM Production.TransactionHistoryArchive AS THA 
        WHERE THA.ProductID = P.ProductID
 
        UNION ALL
 
        SELECT 1
        FROM Production.TransactionHistory AS TH 
        WHERE TH.ProductID = P.ProductID
    )
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8608, QUERYTRACEON 8612, QUERYTRACEON 8621);

La sottoquery esistente viene prima trasformata in un'applicazione: