sql >> Database >  >> RDS >> Database

Rijdoelen, deel 3:Anti-joins

Dit bericht maakt deel uit van een reeks artikelen over rijdoelen. De andere onderdelen vind je hier:

  • Deel 1:Rijdoelen stellen en identificeren
  • Deel 2:Semi-joins

Dit deel behandelt wanneer en waarom de optimizer een rijdoel voor een anti-join introduceert.

Inleiding

Een anti-join wordt ook wel een anti-semi-join genoemd. Het retourneert elke rij van join-invoer A waarvoor geen overeenkomst is te vinden op ingang B.

Voor een anti-join:

  • De optimizer mag voeg een doel aan de binnenzijde toe aan een solliciteer (gecorreleerde geneste loops join) alleen anti-join .
  • Een rijdoel is niet toegevoegd voor niet-gecorreleerde geneste lussen anti join, hash anti join of merge anti join.
  • Zoals altijd wordt elk rijdoel alleen toegevoegd als deze lager is dan de schatting zonder dat een rijdoel is toegepast.
  • Overtollige binnenzijde TOP clausules en DISTINCT/GROUP BY bewerkingen kunnen worden vereenvoudigd.

Voortbouwend op het eerste opsommingsteken hierboven, is het belangrijkste verschil tussen semi-join toepassen en anti-join rijdoelen toepassen:

  • Een apply semi join heeft altijd een rijdoel (zolang het minder is dan de schatting zonder het doel).
  • Een apply anti join kan een rijdoel bevatten , maar alleen als een logische anti-join wordt getransformeerd in een toepassing tijdens optimalisatie op basis van kosten .

Het spijt me dat deze regels niet eenvoudiger zijn, maar ik heb ze niet gemaakt. Hopelijk maken enkele discussies en voorbeelden het allemaal duidelijker.

Standaard geen anti-join rijdoel

De optimizer gaat ervan uit dat mensen een semi-join . schrijven (indirect bijv. met behulp van EXISTS ) met de verwachting dat de rij waarnaar wordt gezocht zal worden gevonden . Een semi-join toepassen rijdoel is ingesteld door de optimizer om die verwachte overeenkomende rij snel te vinden.

Voor anti-join (uitgedrukt bijvoorbeeld met behulp van NOT EXISTS ) de veronderstelling van de optimizer is dat een overeenkomende rij niet zal worden gevonden . Een anti-join toepassen rijdoel is niet ingesteld door de optimizer, omdat deze verwacht alle rijen te moeten controleren om te bevestigen dat er geen overeenkomst is.

Als er wel een overeenkomende rij blijkt te zijn, kan het toepassen van anti-join langer duren om deze rij te vinden dan wanneer een rijdoel was gebruikt. Desalniettemin zal de anti-join zijn zoekactie nog steeds beëindigen zodra de (onverwachte) overeenkomst wordt gevonden.

Toepassen Anti Join Row Goal-voorwaarden

SQL Server biedt ons geen manier om rechtstreeks een anti-join te schrijven, dus we moeten tijdelijke oplossingen gebruiken zoals NOT EXISTS , NOT IN/ANY/SOME , of EXCEPT . Elk van deze vormen resulteert in een weergave van een subquery in de geparseerde structuur aan het begin van de querycompilatie. Deze subquery wordt altijd uitgerold in een Apply, en vervolgens omgezet in een logische anti-join waar mogelijk (de details zijn hetzelfde als voor semi-join besproken in deel 2). Dit gebeurt allemaal voordat zelfs maar een triviaal plan is overwogen.

Om ervoor te zorgen dat een anti-join een row-doel krijgt, moet deze invoeren op kosten gebaseerde optimalisatie als een logische anti-join (wat betekent dat de transformatie van een toepassing hierboven succesvol moet zijn geweest). Vervolgens moet de op kosten gebaseerde optimalisatie ervoor kiezen om de logische anti-join te implementeren als een apply . Om dit te laten gebeuren, moet de optimizer er eerst voor kiezen om verkennen de toepassingsoptie; dan moet het selecteren dat als de goedkoopste optie (voor dat deel van het plan).

Een anti-joinrijdoel wordt ingesteld door een van de op kosten gebaseerde optimalisatieregels die een join kunnen transformeren in een Apply. Een anti-join die binnenkomt op kosten gebaseerde optimalisatie als een toepassing (omdat de transformatie naar logische anti-join is mislukt) zal niet een rijdoel toepassen.

De op kosten gebaseerde optimalisatie zal alleen de optie samenvoegen om toe te passen verkennen en selecteren als er een efficiënte manier is om overeenkomende rijen aan de binnenkant te vinden (bijvoorbeeld door een index te gebruiken). Het optimalisatieprogramma zal de optie niet onderzoeken als de subboom aan de binnenkant van de join iets nuttigs mist voor het predikaat van toepassing op "vergrendelen op". Dit kan een index zijn, een tijdelijke index (via een gretige indexspoel) of een andere logische sleutel. Door het rijdoel toe te voegen, kan de optimizer de kosten van de optie 'Join-to-Apply' inschatten, aangezien er maximaal één rij moet worden gevonden.

Houd er rekening mee dat een anti-join toepassen in een uitvoeringsplan kan voorkomen zonder een rijdoel. Dit gebeurt wanneer de initiële transformatie van Apply naar Join mislukt, wat relatief vaak voorkomt. Wanneer dit gebeurt, begint de anti-join het leven in de op kosten gebaseerde optimalisatie als een toepassing, en er wordt dus nooit een rijdoel toegevoegd door een van de regels voor samenvoegen om toe te passen.

Uiteraard kan aan de binnenzijde van deze aanvraag ook een rijdoel worden ingevoerd via een ander mechanisme (niet gekoppeld aan de aanvraag), bijvoorbeeld door een aparte Top-operator.

Samenvattend:

  • Een anti-join kan alleen een rijdoel behalen tijdens kostengebaseerde optimalisatie (CBO).
  • Regels die een anti-join vertalen naar een toepassing, voegen een rijdoel toe.
  • De anti-join moet CBO invoeren als een join, niet als een toepassing.
  • Om CBO als join in te voeren, moeten eerdere fasen de subquery kunnen herschrijven als join (via een toepassingsfase).
  • CBO verkent alleen de join om transformatie toe te passen in veelbelovende gevallen.

Voorbeeld

Het is wat lastiger om dit allemaal aan te tonen voor anti-join toepassen dan voor semi-join toepassen. De redenen hiervoor komen aan bod in deel 4.

Ondertussen is hier een AdventureWorks-voorbeeld dat laat zien hoe een anti-join toepassen met rijdoel ontstaat, met dezelfde ongedocumenteerde traceervlaggen als voor semi-join. Traceringsvlag 8608 is toegevoegd om de initiële memostructuur weer te geven aan het begin van op kosten gebaseerde optimalisatie.

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);

De bestaande subquery wordt eerst omgezet in een Apply: