Bu sayfa makine çevirisidir. İngilizce orijinalini okuyun. English

IBSurgeon kütüphanesi

Firebird 5.0.1 Optimizer İyileştirmeleri

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

Geçtiğimiz günlerde Firebird 5.0 DBMS’nin bir nokta sürümü yayınlandı Firebird 5.0.1. Hata düzeltmelerinin yanı sıra, bu makalede ele alınacak yeni bir deneysel optimizasyon işlevi eklendi.

Alt sorguların ANY/SOME/IN/EXISTS içinde yarı birleştirmeye (semi-join) dönüştürülmesi

Yarı birleştirme, iki ilişkiyi birleştiren ve birleştirmenin tamamını gerçekleştirmeden yalnızca bir ilişkiden satırlar döndüren bir işlemdir. Diğer birleştirme operatörlerinden farklı olarak, yarı birleştirme yapılıp yapılmayacağını belirten açık bir sözdizimi yoktur. Ancak, ANY/SOME/IN/EXISTS içindeki alt sorguları kullanarak yarı birleştirme gerçekleştirebilirsiniz.

Geleneksel olarak Firebird, ANY/SOME/IN koşullarındaki alt sorguları EXISTS koşulundaki ilişkili alt sorgulara dönüştürür ve EXISTS içindeki alt sorguyu dış sorgunun her kaydı için çalıştırır. EXISTS koşulu içindeki bir alt sorgu çalıştırılırken FIRST ROWS stratejisi kullanılır ve ilk kayıt döndürüldükten hemen sonra yürütme durur.

Firebird 5.0.1 ile başlayarak, ANY/SOME/IN/EXISTS koşullarındaki alt sorgular yarı birleştirmelere dönüştürülebilir. Bu özellik varsayılan olarak devre dışıdır ve firebird.conf veya database.conf dosyasında SubQueryConversion yapılandırma parametresi true olarak ayarlanarak etkinleştirilebilir.

Bu özellik deneyseldir, bu nedenle varsayılan olarak devre dışıdır. Etkinleştirip ANY/SOME/IN/EXISTS koşullarında alt sorgular içeren sorgularınızı test edebilirsiniz; performans daha iyiyse etkin bırakın, aksi takdirde SubQueryConversion parametresini varsayılan değere (false) geri ayarlayın.
SubQueryConversion yapılandırma parametresinin varsayılan değeri gelecekte değiştirilebilir veya parametre tamamen kaldırılabilir. Bu, yeni yöntemin çoğu durumda daha optimal olduğu kanıtlandığında gerçekleşecektir.

ANY/SOME/IN/EXISTS işlemlerini doğrudan alt sorgular üzerinde, yani ilişkili alt sorgular olarak gerçekleştirmenin aksine, bunları yarı birleştirmeler olarak gerçekleştirmek optimizasyon için daha fazla alan sağlar. Yarı birleştirmeler çeşitli Hash Join (semi) veya Nested Loop Join (semi) algoritmalarıyla gerçekleştirilebilirken, ilişkili alt sorgular her zaman dış sorgunun her kaydı için çalıştırılır.

Bu özelliği firebird.conf dosyasında SubQueryConversion parametresini true olarak ayarlayarak etkinleştirmeyi deneyelim. Şimdi bazı deneyler yapalım.

Aşağıdaki sorguyu çalıştıralım:

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|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Yürütme planında yeni bir birleştirme yöntemi olan Hash Join (semi) görüyoruz. IN içindeki alt sorgunun sonucu arabelleğe alındı ve bu, planda Record Buffer (record length: 41) olarak görünüyor. Yani bu durumda IN içindeki alt sorgu bir kez çalıştırıldı, sonucu hash tablosu belleğine kaydedildi ve ardından dış sorgu bu hash tablosunda arama yaptı.

Karşılaştırma için, alt sorgudan yarı birleştirmeye dönüştürme devre dışıyken aynı sorguyu çalıştıralım.

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|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Yürütme planı, alt sorgunun ana sorgunun her kaydı için çalıştırıldığını ancak ek bir FK_COVER_FATHER dizini kullandığını gösteriyor. Bu, yürütme istatistiklerinde de görülebilir: Fetches sayısı 4 kat daha fazla, yürütme süresi neredeyse 4 kat daha kötü.

Okuyucu sorabilir: hash yarı birleştirmesi neden COVER tablosunun 5 kat daha fazla dizin okuması gösteriyor, ancak diğer açılardan daha iyi? Gerçek şu ki, istatistiklerdeki dizin okumaları dizin kullanılarak okunan kayıt sayısını gösterir; bunlar, hiç kayıt getirmeyen ancak ücretsiz olmayan toplam dizin erişim sayısını göstermez.

Ne oldu? Alt sorguların dönüşümünü daha iyi anlamak için hayali bir “SEMI JOIN” yarı birleştirme operatörü tanıtalım. Daha önce de söylediğim gibi, bu birleştirme türü SQL dilinde temsil edilmez. IN operatörlü sorgumuz, aşağıdaki gibi yazılabilen eşdeğer bir forma dönüştürüldü:

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

Şimdi daha net. EXISTS kullanan alt sorgular için de aynı şey olur. Başka bir örneğe bakalım:

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
  )

Şu anda böyle bir EXISTS’i IN kullanarak yazmak mümkün değil. Bunun yarı birleştirmeye dönüştürülmeden nasıl uygulandığını görelim.

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|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Çok yavaş. Şimdi SubQueryConversion = true ayarlayalım ve sorguyu tekrar çalıştıralım.

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|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Sorgu 100 kat daha hızlı çalıştı! Hayali SEMI JOIN operatörümüzü kullanarak yeniden yazarsak, sorgu şöyle görünecektir:

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

IN/EXISTS içindeki herhangi bir ilişkili alt sorgu yarı birleştirmeye dönüştürülebilir mi? Hayır, herhangi biri değil; örneğin, alt sorgu FETCH/FIRST/SKIP/ROWS filtreleri içeriyorsa, alt sorgu yarı birleştirmeye dönüştürülemez ve ilişkili alt sorgu olarak çalıştırılır. İşte böyle bir sorgu örneği:

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
  )

Burada OFFSET 0 ROWS ifadesi sorgunun anlamını değiştirmez ve yürütme sonucu onsuz olduğu gibi aynı olacaktır. Bu sorgunun planına ve istatistiklerine bakalım.

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

Geçerli bellek = 551912944
Delta bellek = 288
Maksimum bellek = 552002112
Geçen süre = 0.201 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 408988
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |    10971|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Gördüğünüz gibi, yarı birleştirmeye dönüşüm gerçekleşmedi. Şimdi OFFSET 0 ROWS ifadesini kaldıralım ve istatistikleri tekrar alalım.

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: 33)
                    -> Table "COVER" Full Scan

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

Geçerli bellek = 552112128
Delta bellek = 288
Maksimum bellek = 585044592
Geçen süre = 0.405 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 854841
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |   722465|         |         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Burada yarı birleştirmeye dönüşüm gerçekleşti ve gördüğümüz gibi çalışma süresi daha da kötüleşti. Bunun nedeni, şu anda sorgu iyileştiricisinin Hash Join (semi) ve dizin kullanan Nested Loop Join (semi) birleştirme algoritmaları arasında bir maliyet tahmini yapmamasıdır. Bu nedenle kural şudur: birleştirme koşulu yalnızca eşitlik içeriyorsa, Hash Join (semi) algoritması seçilir; aksi takdirde IN/EXISTS alt sorguları normal şekilde yürütülür.

Şimdi yarı birleştirme dönüşümünü devre dışı bırakalım ve çalıştırma istatistiklerine bakalım.

Code
Sub-query
    -> 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

Geçerli bellek = 551912752
Delta bellek = 288
Maksimum bellek = 552001920
Geçen süre = 0.193 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 408988
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |    10971|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Gördüğünüz gibi, Getirmeler, alt sorgunun OFFSET 0 ROWS ifadesini içerdiği durumla tamamen aynıdır ve çalışma süresi hata payı dahilinde farklılık gösterir. Bu, OFFSET 0 ROWS ifadesini yarı birleştirme dönüşümünü devre dışı bırakmak için bir ipucu olarak kullanabileceğiniz anlamına gelir.

Şimdi alt sorgularda eşitlik ve IS NOT DISTINCT FROM dışında herhangi bir ilişkili koşulun kullanıldığı durumlara bakalım.

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
        -> Table "COVER" Access By ID
            -> Bitmap
                -> Index "COVER_IDX_BYDATE" Range Scan (lower bound: 1/1)
Select Expression
    -> Aggregate
        -> Filter
            -> Table "HORSE" as "H" Access By ID
                -> Bitmap
                    -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)

Yukarıda söylediğim gibi, yarı birleştirmeye dönüşüm gerçekleşmedi; alt sorgu ana sorgunun her kaydı için yürütülür.

Deneylere devam edelim, eşitlik ve eşitlik dışında bir koşul daha kullanan bir sorgu yazalım.

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
                -> Table "HORSE" as "H" Access By ID
                    -> Bitmap
                        -> Index "FK_HORSE_DEPARTURE" Range Scan (full match)
            -> Filter
                -> Filter
                    -> Table "COVER" Access By ID
                        -> Bitmap
                            -> Index "COVER_IDX_BYDATE" Range Scan (lower bound: 1/1)

Burada planda Nested Loop Join (semi) birleştirme yönteminin ilk kullanımını görüyoruz, ancak maalesef bu plan kötüdür çünkü FK_COVER_FATHER dizini kullanılmamaktadır. Böyle bir sorgudan herhangi bir sonuç alamazsınız. Bu, OFFSET 0 ROWS ipucu kullanılarak düzeltilebilir.

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
            -> 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
=====================
                72199

Geçerli bellek = 554017824
Delta bellek = 320
Maksimum bellek = 554284480
Geçen süre = 45.548 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 84145713
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         | 75894621|         |         |         |
HORSE                           |         |    96021|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

En iyi çalışma süresi değil, ancak bu durumda en azından sonucu elde ettik.

Bu nedenle, alt sorguları ANY/SOME/IN/EXISTS’ten yarı birleştirmeye dönüştürmek, bazı durumlarda sorgu yürütmeyi önemli ölçüde hızlandırabilir, ancak şu anda bu özellik hâlâ kusurludur ve bu nedenle varsayılan olarak devre dışıdır. Firebird 6.0’da bu özellik için maliyet tahmini eklemeye ve bir dizi başka eksikliği gidermeye çalışacaklar. Ayrıca Firebird 6.0’da ALL/NOT IN/NOT EXISTS alt sorgularının anti-birleştirmeye dönüştürülmesinin eklenmesi planlanmaktadır.

IN/EXISTS içindeki alt sorguların yürütülmesine ilişkin incelemenin sonucunda, şu biçimde bir sorgunuz varsa

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

veya

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

bu tür sorguların neredeyse her zaman şu şekilde yürütülmesinin daha verimli olduğunu belirtmek isterim:

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

Size net bir örnek vereyim:

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

Hash Join (semi) kullanarak çalıştırma planı ve istatistikleri

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

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

Geçerli bellek = 554176768
Delta bellek = 288
Maksimum bellek = 555531328
Geçen süre = 0.229 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 569683
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |     6695|         |         |         |
HORSE                           |   525875|         |         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Oldukça hızlı, ancak HORSE tablosu tamamen okunuyor.

Klasik alt sorgu yürütme ile çalıştırma planı ve istatistikleri

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" Full Scan

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

Geçerli bellek = 553472512
Delta bellek = 288
Maksimum bellek = 553966592
Geçen süre = 6.862 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 2462726
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+
COVER                           |         |     1616|         |         |         |
HORSE                           |   525875|         |         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Çok yavaş. HORSE tablosu tam tarama yapıyor ve alt sorgu birden çok kez yürütülüyor - HORSE tablosundaki her kayıt için.

Ve şimdi DISTINCT ile hızlı seçenek

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 (record length: 44, key length: 12)
                -> Filter
                    -> Table "COVER" as "TMP COVER" Access By ID
                        -> Bitmap
                            -> Index "IDX_COVER_BYYEAR" Range Scan (full match)
            -> Filter
                -> Table "HORSE" as "H" Access By ID
                    -> Bitmap
                        -> Index "PK_HORSE" Unique Scan

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

Geçerli bellek = 554349728
Delta bellek = 320
Maksimum bellek = 555531328
Geçen süre = 0.011 sn
Tamponlar = 32768
Okumalar = 0
Yazmalar = 0
Getirmeler = 14954
Tablo bazında istatistikler:
--------------------------------+---------+---------+---------+---------+---------+
 Tablo adı                      | Doğal   | Dizin   | Ekleme  | Güncelleme | Silme  |
--------------------------------+---------+---------+---------+---------+---------+

COVER                           |         |     6695|         |         |         |
HORSE                           |         |     1616|         |         |         |
--------------------------------+---------+---------+---------+---------+---------+

Gereksiz okumalar yok, sorgu çok hızlı çalıştırılıyor. Dolayısıyla sonuç şudur - IN/EXISTS/ANY/SOME içindeki alt sorguların yürütme planına her zaman bakın ve sorgu yazmanın alternatif varyantlarını kontrol edin.