İçeriğe geç

Sorgu Planı ve Join Algoritmaları — Planner Neden Bu Yolu Seçti?

İleri 9 dk Sık karşılaşılır

Önce şunu oku: SQL İndeksleme ve EXPLAIN

30 saniyede özet

Veritabanı iki tabloyu üç farklı yolla birleştirebilir ve hangisini seçeceğine 'kaç satır gelir?' tahminiyle karar verir. Tahmin yanlışsa beş kişilik plan beş yüz kişiye uygulanır. Planı okurken önce tahmini gerçekle karşılaştır.

Gece toplu yükleme yapıldı ve sabah raporu saatlerce bitmedi. Dün aynı sorgu iki saniyede bitiyordu. Kod değişmemişti.

  1. Bayt: Sabah raporu saatlerce bitmedi! Dün aynı sorgu iki saniyede bitiyordu.

  2. Sen: Kodda ne değişti?

  3. Bayt: Hiçbir şey! Sadece gece büyük bir yükleme yapıldı.

  4. Bayt: Beş misafir bekleyip beş yüz ağırlarsan alışverişi nasıl yaparsın?

Birleştirmenin üç yolu

AlgoritmaNasıl çalışırİyi olduğu yer
Nested loopDıştaki her satır için içeride ararDış taraf küçük, iç tarafta index var
Hash joinKüçük tarafı bellekte hash tablosuna koyar, büyük tarafı bir kez tararİki taraf da büyük, eşitlik koşulu
Merge joinİki sıralı girdiyi fermuar gibi birleştirirGirdiler zaten sıralı (ör. index sırası)

Hangisinin iyi olduğu satır sayısına bağlıdır. Beş sipariş için index’te beş arama, milyon satırı taramaktan ucuzdur; yüz bin sipariş için tersi.

Kafam karıştı, daha basit anlat

Beş kişiyi bulmak için telefon rehberinde tek tek aramak hızlıdır. Yüz bin kişiyi bulacaksan rehberi baştan sona okumak daha hızlı olabilir.

Hızlı kontrolOrta

Bir müşterinin 5 siparişini, 10 milyon satırlık order_items tablosuyla birleştiriyorsun; order_items(order_id) üzerinde index var. Hangi join algoritması büyük ihtimalle en ucuzu?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Karar bir tahmine dayanır

query plannerBir SQL sorgusu için olası yürütme planlarının maliyetini tahmin edip en ucuz görüneni seçen bileşen. Maliyet tabloların istatistiklerine dayanır.Sözlükte gör →, her olası planın maliyetini hesaplar ve en ucuzunu seçer. Maliyetin temel girdisi, her adımda kaç satır geleceğine dair satır tahminiPlanner'ın bir plan adımından kaç satır çıkacağına dair tahmini. İstatistikler eskiyse tahmin yanlış olur ve maliyet hesabı onunla birlikte yanılır.Sözlükte gör →.

Tablo dün boştu ve istatistikleri o zaman toplandı. Gece yüz bin sipariş yüklendi. Planner bugünün siparişleri için kaç satır bekler? Cevabı göster

Neredeyse hiç. İstatistikler güncellenmediyse planner tabloyu hâlâ boş sanır ve bir satır için en ucuz planı seçer. Index yoksa bu, yüz bin sipariş için milyon satırlık tabloyu yüz bin kez taramak demektir.

Plan, kaç misafir geleceği tahminine göre yapılır.
Adım adım oku
  1. İstatistikler tablo boşken toplanmış: planner beş satır bekliyor ve tezgâh tezgâh dolaşan nested loop'u seçiyor.
  2. Gece yüz bin sipariş yüklendi ama planner'ın defteri güncellenmedi.
  3. Nested loop çok sayıda satırda tek tek dolaşmaya devam eder; rapor saatler sürer.
  4. ANALYZE ile istatistikler yenilenince planner gerçek sayıyı görür ve toptancıya gider gibi hash join'i seçer.

Autovacuum ya da otomatik istatistik güncellemesi eninde sonunda devreye girer. Ama toplu yüklemeden hemen sonra çalışan bir sorgu onu beklemez.

Kafam karıştı, daha basit anlat

Veritabanı hangi yolun ucuz olduğuna tahminle karar verir. Elindeki bilgi eskiyse, yanlış yolu seçer. Bu yüzden istatistikler güncel tutulmalı.

Hızlı kontrolİleri

Gece toplu yükleme sonrası sabah raporu, dün iki saniye sürerken bugün saatlerce sürüyor. Kod ve index'ler aynı. En olası sebep?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Planner tahminle seçer, sorgu gerçekle çalışır

Tohum 625264

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

orders ⋈ order_items (1.000.000 satır)

Planner, süzgeçten kaç sipariş çıkacağını tahmin edip en ucuz görünen join algoritmasını seçecek.

Görevler0/3

  • Planner'a en iyisinden en az 10 kat pahalı bir plan seçtiraçık

    İpucu

    Tahmin ile gerçek arasındaki farkı büyüt.

  • Nested loop'un en doğru seçim olduğu durumu bulaçık

    İpucu

    Az satır ve arama yapılabilecek bir yol.

  • Çok satırda planner doğru biçimde hash join seçsinaçık

    İpucu

    İstatistikler gerçeği bilsin.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Eski istatistik, index yok: planner nested loop seçti, gerçek iş on binlerce kat fazla.
  2. İstatistikleri güncelle. Planner hash join’e geçti.
  3. Eski istatistiğe dön, index’i aç. Yine yanlış plan, ama bu kez hasar küçük.
  4. Sipariş sayısını “az” yap. Nested loop gerçekten en iyi seçim.
Hızlı kontrolİleri

Simülatörde istatistikler eskiyken index açılınca planner yine yanlış plan seçti ama hasar küçük kaldı. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Plan nasıl okunur?

Üç motorun da planı gerçek satır sayılarıyla gösteren bir yolu var:

-- PostgreSQL
EXPLAIN (ANALYZE, BUFFERS) SELECT …;
-- SQL Server (SSMS'te "Include Actual Execution Plan" ya da)
SET STATISTICS XML ON;
-- Oracle
SELECT /*+ gather_plan_statistics */ …;
SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR(format => 'ALLSTATS LAST'));

İlk bakacağın yer, her düğümde tahmini satır ile gerçek satır arasındaki farktır. Fark büyükse sorun çoğu zaman index değil, istatistiktir.

Bunu bir bankanın sabah mutabakatında izleyelim. Gece kart işlemleri toplu yükleniyor, sabah da her işlemin kart kuruluşunun takas dosyasında karşılığı var mı diye bakılıyor.

Derinleş · Kart takas mutabakatı: yükle, ANALYZE et, birleştir 4 dosya · ~62 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/card/ ClearingFileLoader.java Gece yükleyicisi: takas dosyasını COPY ile alır ve hemen ardından ANALYZE çalıştırır.

src/main/java/com/bank/card/ClearingFileLoader.java
@Component
class ClearingFileLoader {
private final DataSource dataSource;
ClearingFileLoader(DataSource dataSource) {
this.dataSource = dataSource;
}
void load(Path clearingFile) throws SQLException, IOException {
try (Connection c = dataSource.getConnection();
Reader in = Files.newBufferedReader(clearingFile)) {
CopyManager copy = c.unwrap(PGConnection.class).getCopyAPI();
copy.copyIn("""
COPY card_transaction (card_token, auth_code, amount, currency, clearing_date)
FROM STDIN (FORMAT csv, HEADER true)
""", in);
// The planner's statistics still describe yesterday's table.
// Autovacuum will refresh them later; the morning reconciliation will not wait.
try (Statement s = c.createStatement()) {
s.execute("ANALYZE card_transaction");
}
}
}
}

src/main/resources/sql/ reconciliation.sql Mutabakat sorgusu: takas kaydında karşılığı olmayan kart işlemlerini bulur.

src/main/resources/sql/reconciliation.sql
-- Every cleared card transaction must match one line of the scheme's settlement file.
-- amount is NUMERIC(19,4) on both sides, so the equality is exact.
SELECT t.id, t.card_token, t.amount, t.currency
FROM card_transaction t
LEFT JOIN settlement_record s
ON s.auth_code = t.auth_code
AND s.amount = t.amount
AND s.currency = t.currency
AND s.settlement_date = t.clearing_date
WHERE t.clearing_date = :clearingDate
AND s.id IS NULL; -- unmatched rows go to the operations queue

explain-after-analyze.txt ANALYZE sonrası beklenen plan: tahmin ve gerçek yakın, iki tablo birer kez okunuyor.

explain-after-analyze.txt
EXPLAIN (ANALYZE, BUFFERS), after ANALYZE card_transaction. Expected shape:
Hash Anti Join
Hash Cond: (t.auth_code = s.auth_code AND t.amount = s.amount
AND t.currency = s.currency AND t.clearing_date = s.settlement_date)
-> Seq Scan on card_transaction t
Filter: (clearing_date = $1)
-> Hash
-> Seq Scan on settlement_record s
What to check on every node:
- estimated rows and actual rows are close
- loops=1: each table is read once, the join is one pass over the hash

counter-example/ explain-stale-stats.txt Şöyle de olabilirdi: ANALYZE yok; bak, planner tabloyu hâlâ boş sanıyor.

counter-example/explain-stale-stats.txt
The same query right after the load, without ANALYZE. Shape:
Nested Loop Anti Join
-> Seq Scan on card_transaction t
Filter: (clearing_date = $1)
estimated rows: 1 <- statistics from when today's rows did not exist
actual rows: the whole night's clearing file
-> Seq Scan on settlement_record s
loops: one per card transaction <- the settlement table is scanned again and again
The plan is cheapest for one row. Nothing in the code changed;
only the planner's picture of the table is out of date.
Kafam karıştı, daha basit anlat

Planı okurken iki sayıyı yan yana koy: veritabanının tahmin ettiği satır sayısı ve gerçekten gelen. Arada büyük fark varsa sorun oradadır.

Hızlı kontrolİleri

PostgreSQL'de `EXPLAIN ANALYZE` çıktısında bir düğüm `rows=1` tahmin edip `actual rows=184000` gösteriyor ve üstünde bir Nested Loop var. İlk ne yaparsın?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hash join için ayrılan bellek, küçük tarafın hash tablosunu almaya yetmezse ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

Toplu yüklemeden sonra istatistik yok. Yüklemenin sonuna ANALYZE (PostgreSQL), UPDATE STATISTICS (SQL Server) ya da DBMS_STATS.GATHER_TABLE_STATS (Oracle) ekle.

parameter sniffingSQL Server'ın bir sorgunun planını ilk çağrıdaki parametre değerine göre derleyip önbellekte tutması. Plan o değere uygunsa diğerlerinde kötü çalışabilir.Sözlükte gör →. SQL Server planı ilk gelen parametreye göre derleyip önbelleğe alır; nadir bir müşteri için seçilen plan, en büyük müşteride çöker.

Kolona fonksiyon. WHERE lower(email) = … düz bir index’i kullanamaz; ifade üzerinde index gerekir.

Tip uyuşmazlığı. SQL Server’da varchar kolonu nvarchar parametreyle karşılaştırmak dönüşüme ve taramaya yol açabilir.

Hızlı kontrolİleri

SQL Server'da bir saklı yordam çoğu müşteri için hızlı, en büyük müşteri için çok yavaş. Yordamı yeniden derleyince bir süre düzeliyor. Olası sebep ve tipik çözümler?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

users(email) üzerinde index var ama giriş sorgusu tüm tabloyu tarıyor. Hatalı satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

login.sql
SQLUTF-8LF

Kendini sına

Şimşek turu1/5

Veritabanı birleştirme yöntemini, kaç satır geleceği tahminine göre seçer.

Soru 1/2Uzman

PostgreSQL'de JDBC üzerinden çalışan bir sorgu ilk birkaç çağrıda hızlı, sonra bir anda yavaşlıyor; psql'den elle çalıştırınca hızlı. Olası açıklama?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aklında kalacak üç şey

  1. 1 Nested loop az satırda ve index varken, hash join çok satırda, merge join sıralı girdilerde iyidir. Hiçbiri her zaman en iyisi değildir.
  2. 2 Veritabanı maliyeti satır tahminiyle hesaplar. İstatistikler eskiyse tahmin yanlış olur ve plan gerçek veri için yanlış seçilir.
  3. 3 Bir planı okurken ilk bakılacak yer, tahmini satır sayısı ile gerçek satır sayısı arasındaki farktır.
Sonraki kapı Siparişin kalemlerini siparişin içine mi koymalı, ayrı mı tutmalı? Doküman Modelleme — Göm mü, Referans mı? · 8 dk

5 kart sonraki derste seni bekliyor

0/5 kart bu dersten toplandı