Deze pagina is automatisch vertaald. Lees het Engelse origineel. English

IBSurgeon-bibliotheek

Firebird 5.0.1-verbeteringen in de optimizer

(c) D.Simonov, IBSurgeon, 21-aug-2024

Onlangs is een point release van het Firebird 5.0 DBMS uitgebracht Firebird 5.0.1. Naast het oplossen van fouten is er een nieuwe experimentele optimalisatiefunctie toegevoegd, die in dit artikel wordt besproken.

Subquery’s converteren naar ANY/SOME/IN/EXISTS in semi-join

Een semi-join is een bewerking die twee relaties samenvoegt en alleen rijen uit één van de relaties retourneert zonder de volledige join uit te voeren. In tegenstelling tot andere join-operators is er geen expliciete syntaxis om aan te geven of een semi-join moet worden uitgevoerd. U kunt echter een semi-join uitvoeren met behulp van subquery’s in ANY/SOME/IN/EXISTS.

Traditioneel transformeert Firebird subquery’s in ANY/SOME/IN-predicaten naar gecorreleerde subquery’s in het EXISTS-predicaat en voert de subquery in EXISTS uit voor elk record van de buitenste query. Bij het uitvoeren van een subquery binnen een EXISTS-predicaat wordt de FIRST ROWS-strategie gebruikt en stopt de uitvoering onmiddellijk nadat het eerste record is geretourneerd.

Vanaf Firebird 5.0.1 kunnen subquery’s in ANY/SOME/IN/EXISTS-predicaten worden geconverteerd naar semi-joins. Deze functie is standaard uitgeschakeld en kan worden ingeschakeld door de configuratieparameter SubQueryConversion op true te zetten in het bestand firebird.conf of database.conf.

Deze functie is experimenteel en daarom standaard uitgeschakeld. U kunt deze inschakelen en uw query’s met subquery’s in ANY/SOME/IN/EXISTS-predicaten testen. Als de prestaties beter zijn, laat u deze ingeschakeld; anders zet u de parameter SubQueryConversion terug naar de standaardwaarde ( false).
De standaardwaarde voor de configuratieparameter SubQueryConversion kan in de toekomst worden gewijzigd, of de parameter kan volledig worden verwijderd. Dit zal gebeuren zodra de nieuwe werkwijze in de meeste gevallen bewezen optimaler te zijn.

In tegenstelling tot het direct uitvoeren van ANY/SOME/IN/EXISTS op subquery’s, d.w.z. als gecorreleerde subquery’s, biedt het uitvoeren ervan als semi-joins meer ruimte voor optimalisatie. Semi-joins kunnen worden uitgevoerd met verschillende Hash Join (semi)- of Nested Loop Join (semi)-algoritmen, terwijl gecorreleerde subquery’s altijd voor elk record van de buitenste query worden uitgevoerd.

Laten we proberen deze functie in te schakelen door de parameter SubQueryConversion op true te zetten in het bestand firebird.conf. Laten we nu wat experimenten doen.

Laten we de volgende query uitvoeren:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND H.CODE_SEX = 2
  AND H.CODE_HORSE IN (
    SELECT COVER.CODE_FATHER
    FROM COVER
    WHERE COVER.CODE_DEPARTURE = 1
      AND EXTRACT(YEAR FROM COVER.BYDATE) = 2023
  )
Code
Select Expression
    -> Aggregate
        -> Filter
            -> Hash Join (semi)
                -> Filter
                    -> Table "HORSE" as "H" Access By ID
                        -> Bitmap And
                            -> Bitmap
                                -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)
                            -> Bitmap
                                -> Index "FK_HORSE_SEX" Range Scan (full match)
                -> Record Buffer (record length: 41)
                    -> Filter
                        -> Table "COVER" Access By ID
                            -> Bitmap And
                                -> Bitmap
                                    -> Index "IDX_COVER_BYYEAR" Range Scan (full match)
                                -> Bitmap
                                    -> Index "FK_COVER_DEPARTURE" Range Scan (full match)

                COUNT
=====================
                  297

Current memory = 552356752
Delta memory = 352
Max memory = 552567920
Elapsed time = 0.045 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 43984
Per table statistics:
--------------------------------+---------+---------+---------+---------+---------+
 Table name                     | Natural | Index   | Insert  | Update  | Delete  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |     1516|         |         |         |
HORSE                           |         |    37069|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

In het uitvoeringsplan zien we een nieuwe join-methode Hash Join (semi). Het resultaat van de subquery in IN werd gebufferd, wat in het plan zichtbaar is als Record Buffer (record length: 41). Dat wil zeggen dat in dit geval de subquery in IN één keer werd uitgevoerd, het resultaat in het hashtabelgeheugen werd opgeslagen en de buitenste query vervolgens eenvoudigweg in deze hashtabel zocht.

Ter vergelijking voeren we dezelfde query uit met subquery-naar-semi-join-conversie uitgeschakeld.

Code
Sub-query
    -> Filter
        -> Filter
            -> Table "COVER" Access By ID
                -> Bitmap And
                    -> Bitmap
                        -> Index "FK_COVER_FATHER" Range Scan (full match)
                    -> Bitmap
                        -> Index "IDX_COVER_BYYEAR" Range Scan (full match)
Select Expression
    -> Aggregate
        -> Filter
            -> Table "HORSE" as "H" Access By ID
                -> Bitmap And
                    -> Bitmap
                        -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)
                    -> Bitmap
                        -> Index "FK_HORSE_SEX" Range Scan (full match)

                COUNT
=====================
                  297

Current memory = 552046496
Delta memory = 352
Max memory = 552135600
Elapsed time = 0.395 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 186891
Per table statistics:
--------------------------------+---------+---------+---------+---------+---------+
 Table name                     | Natural | Index   | Insert  | Update  | Delete  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |      297|         |         |         |
HORSE                           |         |    37069|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Het uitvoeringsplan laat zien dat de subquery voor elk record van de hoofdquery wordt uitgevoerd, maar gebruikmaakt van een extra index FK_COVER_FATHER. Dit is ook zichtbaar in de uitvoeringsstatistieken: het aantal Fetches is 4 keer groter en de uitvoeringstijd is bijna 4 keer slechter.

De lezer vraagt zich misschien af: waarom toont hash semi-join 5 keer meer indexlezingen van de COVER-tabel, maar is het verder beter? Het feit is dat indexlezingen in statistieken het aantal records tonen dat met behulp van de index is gelezen; ze tonen niet het totale aantal index-toegangen, waarvan sommige helemaal geen records ophalen, maar deze toegangen zijn niet gratis.

Wat is er gebeurd? Om de transformatie van subquery’s beter te begrijpen, introduceren we een denkbeeldige semi-join-operator “SEMI JOIN”. Zoals ik al zei, wordt dit type join niet weergegeven in de SQL-taal. Onze query met de IN-operator werd getransformeerd naar een equivalente vorm, die als volgt kan worden geschreven:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
  SEMI JOIN (
    SELECT COVER.CODE_FATHER
    FROM COVER
    WHERE COVER.CODE_DEPARTURE = 1
      AND EXTRACT(YEAR FROM COVER.BYDATE) = 2023
  ) TMP ON TMP.CODE_FATHER = H.CODE_HORSE
WHERE H.CODE_DEPARTURE = 1
  AND H.CODE_SEX = 2

Nu is het duidelijker. Hetzelfde gebeurt voor subquery’s die EXISTS gebruiken. Laten we naar een ander voorbeeld kijken:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND EXISTS (
    SELECT *
    FROM COVER
    WHERE COVER.CODE_DEPARTURE = 1
      AND COVER.CODE_FATHER = H.CODE_FATHER
      AND COVER.CODE_MOTHER = H.CODE_MOTHER
  )

Momenteel is het niet mogelijk om zo’n EXISTS met IN te schrijven. Laten we zien hoe het wordt geïmplementeerd zonder transformatie naar een semi-join.

Code
Sub-query
    -> Filter
        -> Table "COVER" Access By ID
            -> Bitmap And
                -> Bitmap
                    -> Index "FK_COVER_MOTHER" Range Scan (full match)
                -> Bitmap
                    -> Index "FK_COVER_FATHER" Range Scan (full match)
Select Expression
    -> Aggregate
        -> Filter
            -> Table "HORSE" as "H" Access By ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)

                COUNT
=====================
                91908

Current memory = 552240400
Delta memory = 352
Max memory = 554680016
Elapsed time = 19.083 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 935679
Per table statistics:
--------------------------------+---------+---------+---------+---------+---------+
 Table name                     | Natural | Index   | Insert  | Update  | Delete  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |    91908|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Zeer traag. Laten we nu SubQueryConversion = true instellen en de query opnieuw uitvoeren.

Code
Select Expression
    -> Aggregate
        -> Filter
            -> Hash Join (semi)
                -> Filter
                    -> Table "HORSE" as "H" Access By ID
                        -> Bitmap
                            -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)
                -> Record Buffer (record length: 49)
                    -> Filter
                        -> Table "COVER" Access By ID
                            -> Bitmap
                                -> Index "FK_COVER_DEPARTURE" Range Scan (full match)

                COUNT
=====================
                91908

Current memory = 552102000
Delta memory = 352
Max memory = 561520736
Elapsed time = 0.208 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 248009
Per table statistics:
--------------------------------+---------+---------+---------+---------+---------+
 Table name                     | Natural | Index   | Insert  | Update  | Delete  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |   140254|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

De query werd 100 keer sneller uitgevoerd! Als we het herschrijven met onze fictieve SEMI JOIN-operator, ziet de query er als volgt uit:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
  SEMI JOIN (
    SELECT
      COVER.CODE_FATHER,
      COVER.CODE_MOTHER
    FROM COVER
  ) TMP ON TMP.CODE_FATHER = H.CODE_FATHER AND TMP.CODE_MOTHER = H.CODE_MOTHER
WHERE H.CODE_DEPARTURE = 1

Kan elke gecorreleerde subquery in IN/EXISTS worden geconverteerd naar een semi-join? Nee, niet elke; bijvoorbeeld als de subquery FETCH/FIRST/SKIP/ROWS-filters bevat, kan de subquery niet worden geconverteerd naar een semi-join en wordt deze uitgevoerd als een gecorreleerde subquery. Hier is een voorbeeld van zo’n query:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND EXISTS (
    SELECT *
    FROM COVER
    WHERE COVER.CODE_FATHER = H.CODE_HORSE
    OFFSET 0 ROWS
  )

Hier verandert de zin OFFSET 0 ROWS de semantiek van de query niet en het resultaat van de uitvoering is hetzelfde als zonder. Laten we naar het plan en de statistieken van deze query kijken.

Code
Sub-query
    -> Skip N Records
        -> Filter
            -> Table "COVER" Access By ID
                -> Bitmap
                    -> Index "FK_COVER_FATHER" Range Scan (full match)
Select Expression
    -> Aggregate
        -> Filter
            -> Table "HORSE" as "H" Access By ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)

                COUNT
=====================

10971

Huidig geheugen = 551912944
Delta geheugen = 288
Max geheugen = 552002112
Verstreken tijd = 0.201 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 408988
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |    10971|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Zoals u kunt zien, heeft de transformatie naar een semi-join niet plaatsgevonden. Laten we nu OFFSET 0 ROWS verwijderen en opnieuw statistieken opnemen.

Code
Select Expression
    -> Aggregate
        -> Filter
            -> Hash Join (semi)
                -> Filter
                    -> Tabel "HORSE" als "H" Toegang op ID
                        -> Bitmap
                            -> Index "FK_HORSE_DEPARTURE" Bereikscan (volledige match)
                -> Record Buffer (recordlengte: 33)
                    -> Tabel "COVER" Volledige scan

                COUNT
=====================
                10971

Huidig geheugen = 552112128
Delta geheugen = 288
Max geheugen = 585044592
Verstreken tijd = 0.405 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 854841
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |   722465|         |         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Hier heeft de conversie naar semi-join plaatsgevonden, en zoals we kunnen zien is de uitvoeringstijd slechter geworden. De reden is dat de optimizer momenteel geen kostenraming heeft tussen de Hash Join (semi) en Nested Loop Join (semi) join-algoritmen met behulp van een index, dus de regel is: als de join-voorwaarde alleen gelijkheid bevat, wordt het Hash Join (semi)-algoritme gekozen, anders worden de IN/EXISTS-subquery’s op de gebruikelijke manier uitgevoerd.

Laten we nu de semi-join-conversie uitschakelen en naar de uitvoeringsstatistieken kijken.

Code
Sub-query
    -> Filter
        -> Tabel "COVER" Toegang op ID
            -> Bitmap
                -> Index "FK_COVER_FATHER" Bereikscan (volledige match)
Select Expression
    -> Aggregate
        -> Filter
            -> Tabel "HORSE" als "H" Toegang op ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Bereikscan (volledige match)

                COUNT
=====================
                10971

Huidig geheugen = 551912752
Delta geheugen = 288
Max geheugen = 552001920
Verstreken tijd = 0.193 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 408988
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |    10971|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Zoals u kunt zien, is Ophaalbewerkingen exact gelijk aan het geval waarin de subquery de clausule OFFSET 0 ROWS bevatte, en de uitvoeringstijd verschilt binnen de foutmarge. Dit betekent dat u de clausule OFFSET 0 ROWS kunt gebruiken als een hint om de semi-join-conversie uit te schakelen.

Laten we nu kijken naar gevallen waarin een andere gecorreleerde voorwaarde dan gelijkheid en IS NOT DISTINCT FROM in subquery’s wordt gebruikt.

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND EXISTS (
    SELECT *
    FROM COVER
    WHERE COVER.BYDATE > H.BIRTHDAY
  )
Code
Sub-query
    -> Filter
        -> Tabel "COVER" Toegang op ID
            -> Bitmap
                -> Index "COVER_IDX_BYDATE" Bereikscan (ondergrens: 1/1)
Select Expression
    -> Aggregate
        -> Filter
            -> Tabel "HORSE" als "H" Toegang op ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Bereikscan (volledige match)

Zoals ik hierboven al zei, heeft er geen transformatie naar een semi-join plaatsgevonden; de subquery wordt voor elk record van de hoofdquery uitgevoerd.

Laten we de experimenten voortzetten en een query schrijven met gelijkheid en nog een predicaat naast gelijkheid.

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND EXISTS (
    SELECT *
    FROM COVER
    WHERE COVER.CODE_FATHER = H.CODE_FATHER
      AND COVER.BYDATE > H.BIRTHDAY
  )
Code
Select Expression
    -> Aggregate
        -> Nested Loop Join (semi)
            -> Filter
                -> Tabel "HORSE" als "H" Toegang op ID
                    -> Bitmap
                        -> Index "FK_HORSE_DEPARTURE" Bereikscan (volledige match)
            -> Filter
                -> Filter
                    -> Tabel "COVER" Toegang op ID
                        -> Bitmap
                            -> Index "COVER_IDX_BYDATE" Bereikscan (ondergrens: 1/1)

Hier zien we in het plan het eerste gebruik van de Nested Loop Join (semi)-joinmethode, maar helaas is dit plan slecht, omdat de FK_COVER_FATHER-index niet wordt gebruikt. U zult geen resultaten krijgen van een dergelijke query. Dit kan worden verholpen met de OFFSET 0 ROWS-hint.

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_DEPARTURE = 1
  AND EXISTS (
    SELECT *
    FROM COVER
    WHERE COVER.CODE_FATHER = H.CODE_FATHER
      AND COVER.BYDATE > H.BIRTHDAY
    OFFSET 0 ROWS
  )
Code
Sub-query
    -> Skip N Records
        -> Filter
            -> Tabel "COVER" Toegang op ID
                -> Bitmap
                    -> Index "FK_COVER_FATHER" Bereikscan (volledige match)
Select Expression
    -> Aggregate
        -> Filter
            -> Tabel "HORSE" als "H" Toegang op ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Bereikscan (volledige match)

                COUNT
=====================
                72199

Huidig geheugen = 554017824
Delta geheugen = 320
Max geheugen = 554284480
Verstreken tijd = 45.548 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 84145713
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         | 75894621|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Niet de beste uitvoeringstijd, maar in dit geval hebben we tenminste het resultaat gekregen.

Het omzetten van subquery’s naar ANY/SOME/IN/EXISTS naar semi-join kan dus in sommige gevallen de query-uitvoering aanzienlijk versnellen, maar momenteel is deze functie nog onvolmaakt en daarom standaard uitgeschakeld. In Firebird 6.0 zullen ze proberen kostenraming voor deze functie toe te voegen, evenals een aantal andere tekortkomingen te verhelpen. Bovendien zijn er in Firebird 6.0 plannen om conversie van subquery’s ALL/NOT IN/NOT EXISTS naar anti-join toe te voegen.

Ter afsluiting van de bespreking van de uitvoering van subquery’s in IN/EXISTS, wil ik opmerken dat als u een query van de vorm heeft

Code
SELECT ...
FROM T1
WHERE  IN (SELECT field FROM T2 ...)

of

Code
SELECT ...
FROM T1
WHERE EXISTS (SELECT ... FROM T2 WHERE T1. = T2.field)

dergelijke query’s bijna altijd efficiënter zijn om uit te voeren als

Code
SELECT ...
FROM
  T1
  JOIN (SELECT DISTINCT field FROM T2) tmp ON tmp.field = T1.

Laat me een duidelijk voorbeeld geven:

sql
SELECT
  COUNT(*)
FROM
  HORSE H
WHERE H.CODE_HORSE IN (
  SELECT
    CODE_FATHER
  FROM COVER
  WHERE EXTRACT(YEAR FROM COVER.BYDATE) = 2022
)

Uitvoeringsplan en statistieken met behulp van Hash Join (semi)

Code
Select Expression
    -> Aggregate
        -> Filter
            -> Hash Join (semi)
                -> Tabel "HORSE" als "H" Volledige scan
                -> Record Buffer (recordlengte: 41)
                    -> Filter
                        -> Tabel "COVER" Toegang op ID
                            -> Bitmap
                                -> Index "IDX_COVER_BYYEAR" Bereikscan (volledige match)

                COUNT
=====================
                 1616

Huidig geheugen = 554176768
Delta geheugen = 288
Max geheugen = 555531328
Verstreken tijd = 0.229 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 569683
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |     6695|         |         |         |
HORSE                           |   525875|         |         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Redelijk snel, maar de HORSE-tabel wordt volledig gelezen.

Uitvoeringsplan en statistieken met klassieke subquery-uitvoering

Code
Sub-query
    -> Filter
        -> Filter
            -> Tabel "COVER" Toegang op ID
                -> Bitmap En
                    -> Bitmap
                        -> Index "FK_COVER_FATHER" Bereikscan (volledige match)
                    -> Bitmap
                        -> Index "IDX_COVER_BYYEAR" Bereikscan (volledige match)
Select Expression
    -> Aggregate
        -> Filter
            -> Tabel "HORSE" als "H" Volledige scan

                COUNT
=====================
                 1616

Huidig geheugen = 553472512
Delta geheugen = 288
Max geheugen = 553966592
Verstreken tijd = 6.862 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 2462726
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |     1616|         |         |         |
HORSE                           |   525875|         |         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Zeer traag. De HORSE-tabel wordt volledig gescand en de subquery wordt meerdere keren uitgevoerd - voor elk record in de HORSE-tabel.

En nu een snelle optie met DISTINCT

sql
SELECT
  COUNT(*)
FROM
  HORSE H
  JOIN (
      SELECT
        DISTINCT
        CODE_FATHER
      FROM COVER
      WHERE EXTRACT(YEAR FROM COVER.BYDATE) = 2022
  ) TMP ON TMP.CODE_FATHER = H.CODE_HORSE
Code
Select Expression
    -> Aggregate
        -> Nested Loop Join (inner)
            -> Unique Sort (recordlengte: 44, sleutellengte: 12)
                -> Filter
                    -> Tabel "COVER" als "TMP COVER" Toegang op ID
                        -> Bitmap
                            -> Index "IDX_COVER_BYYEAR" Bereikscan (volledige match)
            -> Filter
                -> Tabel "HORSE" als "H" Toegang op ID
                    -> Bitmap
                        -> Index "PK_HORSE" Unieke scan

                COUNT
=====================
                 1616

Huidig geheugen = 554349728
Delta geheugen = 320
Max geheugen = 555531328
Verstreken tijd = 0.011 sec
Buffers = 32768
Leesbewerkingen = 0
Schrijfbewerkingen = 0
Ophaalbewerkingen = 14954
Statistieken per tabel:
--------------------------------+---------+---------+---------+---------+---------+
 Tabelnaam                      | Natuurlijk | Index   | Invoegen | Bijwerken | Verwijderen |
--------------------------------+---------+---------+---------+---------+---------+

COVER | | 6695| | | | HORSE | | 1616| | | | ——————————–+———+———+———+———+———+

Code

Geen onnodige reads, de query wordt zeer snel uitgevoerd. Vandaar de conclusie - kijk altijd naar het uitvoeringsplan van subquery's in IN/EXISTS/ANY/SOME, en controleer alternatieve varianten van het schrijven van query's.