Məzmuna keç
Educora
Universitet30 dəq20 / 22

Sorğuların optimallaşdırılması və indekslər

B-ağac indeksinin necə işlədiyini, sorğu planını oxumağı, indeksdən istifadə edə bilən (sargable) şərtlər yazmağı, kompozit və örtücü indeksləri və indeksin nə vaxt zərərli olduğunu öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • B-ağacın hündürlüyünü və seçiciliyi hesablayıb indeksin faydasını qiymətləndirmək
  • EXPLAIN QUERY PLAN və EXPLAIN ANALYZE nəticəsini oxumaq, qeyri-sargable şərtləri yenidən yazmaq
  • Kompozit və örtücü indeksləri layihələndirmək və indeks yaratmamağın lazım olduğu halları tanımaq

On milyon sifarişli cədvəldə bir müştərinin sifarişlərini tapan sorğu 8 saniyə çəkir; düzgün indeksdən sonra eyni sorğu millisaniyələrlə işləyir. SQL deklarativ dildir: biz nə istədiyimizi yazırıq, necə tapmağı isə VBİS-in optimallaşdırıcısı seçir. O, bir neçə mümkün planı müqayisə edir və cədvəl statistikası əsasında ən ucuzunu götürür. Bu dərsdə optimallaşdırıcının «düşüncə tərzini» öyrənəcəyik: indeksin daxili quruluşu, planın oxunması və onun işini asanlaşdıran sorğular. Xoş xəbər: bunun üçün xüsusi alət lazım deyil — hər şey EXPLAIN əmrindən və bir neçə sadə qaydadan başlayır.

B-ağac indeksi içəridən

Əksər VBİS-lərdə susmaya görə indeks B-ağacdır (dəqiq desək, B+ ağac): açarlar sıralanmış halda disk səhifələrində saxlanılır, hər daxili səhifədə yüzlərlə açar və aşağı səviyyəyə göstəricilər olur, yarpaqlar isə sətirlərə istinad edir və bir-biri ilə zəncirlənir. Ağac həmişə balanslıdır: kökdən istənilən yarpağa qədər yol eyni uzunluqdadır. Ona görə axtarış bir neçə səhifə oxumaqla bitir, yarpaq zənciri isə aralıq sorğularını (BETWEEN, >, ORDER BY) ucuz edir.

h = ⌈ log N ÷ log f ⌉
burada:
  • hağacın hündürlüyü — bir açarı tapmaq üçün oxunan səhifələrin sayı
  • Nindeksdəki açarların (sətirlərin) sayı
  • fbudaqlanma əmsalı — bir səhifəyə sığan açarların sayı (adətən yüzlərlə)

Hündürlük sətirlərin sayı ilə loqarifmik artır: cədvəl 500 dəfə böyüyəndə ağaca cəmi bir səviyyə əlavə olunur.

Nümunə 1: indeks, yoxsa tam skan?

orders cədvəlində N = 10⁸ sətir var, bir səhifəyə 100 sətir sığır, indeksin budaqlanma əmsalı f = 500-dür. Bir müştərinin 12 sifarişini tapmaq üçün indekslə və indekssiz neçə səhifə oxunur?

Həllini göstər
1) Tam skan: bütün cədvəl oxunur — 10⁸ ÷ 100 = 10⁶ səhifə.
2) Ağacın hündürlüyü: log 10⁸ ÷ log 500 = 8 ÷ 2,70 ≈ 2,96, yuxarı yuvarlaqlaşdırsaq h = 3 səhifə.
3) Tapılan 12 sətir ən pis halda 12 fərqli səhifədədir: cəmi ≈ 3 + 12 = 15 səhifə.
4) Fərq: 10⁶ ÷ 15 ≈ 67 000 dəfə az oxuma. Yoxlama: sətirlərin 30%-i lazım olsaydı, səpələnmiş səhifələri bir-bir oxumaq ardıcıl skandan baha başa gələrdi — optimallaşdırıcı onda tam skanı seçir.
s = n ÷ N
burada:
  • sşərtin seçiciliyi (0 ilə 1 arasında)
  • nşərtə uyğun sətirlərin sayı
  • Ncədvəldəki bütün sətirlər

Kiçik s (az sətir qaytaran şərt) indeks üçün yaxşıdır; s böyükdürsə, tam skan ucuz ola bilər.

Sargable şərtlər

Sargable (search argument able) — indeks ilə axtarışa yararlı şərt deməkdir: sütun müqayisənin bir tərəfində «çılpaq» dayanır. Sütuna funksiya və ya hesab tətbiq edəndə (strftime('%m', order_date) = '03', price * 1.18 > 100) indeksdəki sıralanmış qiymətlər işə yaramır və VBİS hər sətri yoxlamalı olur. Eyni şərti aralıq kimi yenidən yazmaq kifayətdir.

SQL
CREATE INDEX idx_orders_date ON orders (order_date);

-- not sargable: a function on the column
EXPLAIN QUERY PLAN
SELECT id FROM orders
WHERE strftime('%m', order_date) = '03';
▸ Gözlənilən nəticə
id | parent | notused | detail
2 | 0 | 214 | SCAN orders USING COVERING INDEX idx_orders_date
SCAN — indeksin hər yazısı yoxlanılır; indeks yalnız cədvəldən kiçik olduğu üçün seçilib.
SQL
CREATE INDEX idx_orders_date ON orders (order_date);

-- sargable: a range on the bare column
EXPLAIN QUERY PLAN
SELECT id FROM orders
WHERE order_date >= '2025-03-01' AND order_date < '2025-04-01';
▸ Gözlənilən nəticə
id | parent | notused | detail
2 | 0 | 154 | SEARCH orders USING COVERING INDEX idx_orders_date (order_date>? AND order_date<?)
SEARCH — ağacdan aralığın başlanğıcına enib yalnız lazım olan yarpaqlar oxunur. id sütunu SQLite-da hər indeksdə saxlanıldığı üçün indeks «örtücüdür». Birinci üç ədəd versiyadan asılıdır, vacib olan detail sütunudur.

Kompozit və örtücü indekslər

Kompozit indeks bir neçə sütun üzrə sıralanır: (customer_id, order_date) əvvəlcə müştəriyə, eyni müştəri daxilində isə tarixə görə. O, telefon kitabçası kimi işləyir: soyadla, soyad və adla axtarmaq olar, amma yalnız adla yox — bu, sol prefiks qaydasıdır. İndeks sorğunun bütün sütunlarını ehtiva edirsə, VBİS cədvələ heç müraciət etmir — belə indeks örtücü (covering) adlanır. Əlavə olaraq, indeksin sırası ORDER BY ilə üst-üstə düşəndə ayrıca sıralama addımı da aradan qalxır.

Nümunə 2: yavaş sorğunu düzəltmək

Müştəri kabinetindəki sorğu: SELECT id, order_date FROM orders WHERE customer_id = 1 AND strftime('%Y', order_date) = '2025' ORDER BY order_date. Planında SCAN orders və USE TEMP B-TREE FOR ORDER BY var. Onu necə sürətləndirməli?

Həllini göstər
1) Tarix şərtini sargable et: order_date >= '2025-01-01' AND order_date < '2026-01-01'.
2) Bərabərlik sütunu birinci, aralıq və sıralama sütunu ikinci olmaqla kompozit indeks yarat: (customer_id, order_date).
3) İndeks customer_id-yə görə dəqiq, order_date-ə görə aralıqla axtarır və sətirləri artıq tarix sırasında verir — müvəqqəti sıralama yox olur.
4) id də indeksdə olduğu üçün cədvələ müraciət lazım deyil: plan SEARCH ... USING COVERING INDEX göstərir.
SQL
-- before: function on the column, no suitable index
EXPLAIN QUERY PLAN
SELECT id, order_date FROM orders
WHERE customer_id = 1 AND strftime('%Y', order_date) = '2025'
ORDER BY order_date;
▸ Gözlənilən nəticə
id | parent | notused | detail
3 | 0 | 216 | SCAN orders
15 | 0 | 0 | USE TEMP B-TREE FOR ORDER BY
SQL
-- after: sargable range + composite index
CREATE INDEX idx_orders_cust_date ON orders (customer_id, order_date);

EXPLAIN QUERY PLAN
SELECT id, order_date FROM orders
WHERE customer_id = 1
  AND order_date >= '2025-01-01' AND order_date < '2026-01-01'
ORDER BY order_date;
▸ Gözlənilən nəticə
id | parent | notused | detail
3 | 0 | 46 | SEARCH orders USING COVERING INDEX idx_orders_cust_date (customer_id=? AND order_date>? AND order_date<?)

PostgreSQL-də EXPLAIN planı və təxmini dəyəri, EXPLAIN ANALYZE isə sorğunu həqiqətən icra edib real vaxtı göstərir. Aşağıda 1 000 000 sətirli cədvəldə indeksdən əvvəl və sonra alınan tipik nəticə var (rəqəmlər avadanlıqdan asılıdır). cost=başlanğıc..cəmi şərti vahidlərdədir, rows isə optimallaşdırıcının təxminidir; təxmin faktiki rows-dan çox fərqlənirsə, statistika köhnəlib və ANALYZE lazımdır.

SQL
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 42;

CREATE INDEX idx_orders_customer ON orders (customer_id);

EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 42;
Gözlənilən nəticə
Seq Scan on orders  (cost=0.00..20834.00 rows=10 width=24) (actual time=0.018..61.922 rows=12 loops=1)
  Filter: (customer_id = 42)
  Rows Removed by Filter: 999988
Planning Time: 0.081 ms
Execution Time: 61.950 ms

Index Scan using idx_orders_customer on orders  (cost=0.42..44.58 rows=10 width=24) (actual time=0.024..0.039 rows=12 loops=1)
  Index Cond: (customer_id = 42)
Planning Time: 0.210 ms
Execution Time: 0.061 ms
PostgreSQL (işlədilə bilməz, nümunə nəticə). Rows Removed by Filter — boş yerə oxunan sətirlər.

İndeks nə vaxt lazım deyil

  • Kiçik cədvəllər — bir neçə səhifəlik cədvəli skan etmək ağacdan enməkdən ucuzdur.
  • Aşağı seçicilik — is_active kimi iki qiymətli sütunda şərt sətirlərin yarısını qaytarır.
  • Çox yazılan cədvəllər — hər indeks hər INSERT, UPDATE, DELETE-də yenilənir; jurnal və sensor cədvəllərində bu, bahalıdır.
  • Təkrarlanan indekslər — (customer_id, order_date) varsa, ayrıca (customer_id) indeksi artıqdır: sol prefiks onu əvəz edir.
Tapşırıq

order_date üzrə indeks yarat və 2025-ci ilin birinci rübünün (yanvar–mart) sifarişlərini sargable aralıq şərti ilə seç: id və order_date, tarixə və id-yə görə sıralanmış. strftime işlətmə.

Tapşırıq · SQL
CREATE INDEX idx_orders_date ON orders (order_date);

-- slow version: WHERE strftime('%m', order_date) IN ('01', '02', '03')
SELECT id, order_date
FROM orders
-- WHERE: a range on the bare column
ORDER BY order_date, id;
▸ Gözlənilən nəticə
id | order_date
1 | 2025-01-15
2 | 2025-01-15
3 | 2025-02-02
4 | 2025-02-10
5 | 2025-03-05
6 | 2025-03-18
Tapşırıq

products.category sütununda indeksin faydasını qiymətləndir: hər kateqoriya üçün sətirlərin sayını (rows_count) və faizlə seçiciliyi (selectivity_pct, 1 onluq rəqəm) göstər. Sətirlərin sayına görə azalan, sonra kateqoriyaya görə sırala.

Tapşırıq · SQL
SELECT category,
       COUNT(*) AS rows_count
       -- selectivity_pct = 100 * n / N
FROM products
GROUP BY category
ORDER BY rows_count DESC, category;
▸ Gözlənilən nəticə
category | rows_count | selectivity_pct
Electronics | 4 | 40
Accessories | 2 | 20
Stationery | 2 | 20
Games | 1 | 10
Home | 1 | 10

Əsas fikirlər

  • B-ağac balanslıdır: hündürlüyü h = ⌈log N ÷ log f⌉, ona görə axtarış bir neçə səhifə oxumaqla bitir.
  • Seçicilik s = n ÷ N kiçik olanda indeks faydalıdır; böyük olanda optimallaşdırıcı tam skanı seçə bilər.
  • Sargable şərtdə sütun «çılpaq» qalır: funksiyaları və hesabı sabit tərəfə köçür, tarixlər üçün aralıq yaz.
  • Kompozit indeksdə sol prefiks qaydası işləyir: əvvəl bərabərlik, sonra aralıq/sıralama sütunu; örtücü indeks cədvələ müraciəti aradan qaldırır.
  • Planı EXPLAIN ilə yoxla, kiçik, az seçici və çox yazılan cədvəllərdə indeks yaratmaqdan çəkin, dəyişikliyin effektini ölç.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Cədvəldə 250 000 sətir var, budaqlanma əmsalı f = 500-dür. B-ağacın hündürlüyü neçədir?