📖 Indeksiranje kao nauka
U svijetu baza podataka, indeks nije samo "ubrzivač". To je kompleksna fizička struktura koja omogućava Query Optimizeru da donese odluku o najjeftinijem putu do vaših podataka. Bez indeksa, čitanje jedne informacije iz miliona redova je kao traženje igle u plastu sijena; sa indeksom, to je hirurški zahvat koji traje milisekunde.
🔍 Razumijevanje SQL Upita: Temelj
Prije nego što uđemo duboko u indekse, razumijmo kako SQL upiti rade i zašto su indeksi važni. Dobro napisan upit u kombinaciji sa pravilnim indeksiranjem je ključ performansi baze podataka.
Osnovna Struktura Upita
Većina SQL upita slijedi ovu strukturu:
-- Osnovna SELECT struktura upita
SELECT column1, column2, ... -- Koje kolone vratiti
FROM table_name -- Koja tabela/tabele
WHERE condition -- Filtriranje redova
GROUP BY column -- Grupisanje redova
HAVING condition -- Filtriranje grupa
ORDER BY column -- Sortiranje rezultata
OFFSET n ROWS FETCH NEXT m ROWS ONLY; -- Paginacija (straničenje)
🔑 Redoslijed Izvršavanja
Važno je razumjeti da SQL Server ne izvršava klauzule u redoslijedu u kojem ih pišete. Stvarni redoslijed izvršavanja je:
- FROM - Odakle čitati podatke
- WHERE - Filtriranje redova
- GROUP BY - Grupisanje
- HAVING - Filtriranje grupa
- SELECT - Odabir kolona
- ORDER BY - Sortiranje
- OFFSET/FETCH - Paginacija
Ovo objašnjava zašto ne možete koristiti alias iz SELECT-a u WHERE klauzuli - WHERE se izvršava prije SELECT-a!
Razumijevanje Izvršavanja Upita
Kada izvršite upit, SQL Server prolazi kroz ove korake:
- Parsiranje (Parse): Provjerava sintaksu upita
- Optimizacija (Optimize): Query Optimizer pronalazi najbolji plan izvršavanja
- Izvršavanje (Execute): Izvršava plan i vraća rezultate
💡 Pregled Planova Izvršavanja
U SSMS-u, pritisnite Ctrl+M ili kliknite "Include Actual Execution Plan" da vidite kako SQL Server izvršava vaš upit. Ovo pokazuje koje indekse koristi i gdje se javljaju uska grla performansi.
Tipovi planova:
- Estimated Execution Plan: Plan koji SQL Server planira koristiti (Ctrl+L)
- Actual Execution Plan: Plan sa stvarnim statistikama nakon izvršavanja (Ctrl+M)
Uobičajeni Oblici Upita
Postoji nekoliko uobičajenih obrazaca upita koje ćete često koristiti. Razumijevanje ovih obrazaca i kako indeksi pomažu svakom od njih je ključno za optimizaciju performansi.
-- 1. Jednostavno traženje (koristi indeks na EmployeeID)
SELECT FirstName, LastName, Email
FROM HR.Employees
WHERE EmployeeID = 123;
-- Ovaj upit koristi Index Seek - direktno skače na red sa EmployeeID = 123
-- Bez indeksa: Table Scan (čita sve redove)
-- Sa indeksom: Index Seek (direktno na red) - 1000x brže!
-- 2. Upit po rasponu (koristi indeks na CreatedDate)
SELECT ReportID, ReportName, CreatedDate, Status
FROM Stats.Reports
WHERE CreatedDate >= '2024-01-01'
AND CreatedDate < '2024-02-01'
ORDER BY CreatedDate DESC;
-- Indeks na CreatedDate omogućava brzo pronalaženje redova u datom rasponu
-- SQL Server koristi Index Range Scan
-- 3. Pretraga po uzorku (LIKE - može biti sporo bez pravilnog indeksa)
SELECT FirstName, LastName
FROM HR.Employees
WHERE Email LIKE '%@company.com';
-- ⚠️ LIKE sa % na početku NE MOŽE koristiti indekse efikasno!
-- Ovo će uraditi Table Scan ili Index Scan (sporo)
-- Bolje: WHERE Email LIKE 'user%@company.com' (može koristiti indeks)
-- 4. Agregacija (koristi indeks na koloni za grupisanje)
SELECT Department, COUNT(*) AS EmployeeCount, AVG(Salary) AS AvgSalary
FROM HR.Employees
GROUP BY Department
HAVING COUNT(*) > 5;
-- Indeks na Department koloni ubrzava grupisanje
-- SQL Server može koristiti Index Scan umjesto Table Scan
-- 5. JOIN upit (koristi indekse na kolonama za spajanje)
SELECT
e.FirstName,
e.LastName,
d.DepartmentName,
e.Salary
FROM HR.Employees e
INNER JOIN HR.Departments d ON e.DepartmentID = d.DepartmentID
WHERE e.Salary > 50000
ORDER BY e.Salary DESC;
-- Indeksi na e.DepartmentID i d.DepartmentID ubrzavaju JOIN
-- Indeks na e.Salary ubrzava WHERE i ORDER BY
Zašto su Indeksi Važni?
Bez indeksa, SQL Server mora skenirati svaki red u tabeli (table scan). Sa milionima redova, ovo je ekstremno sporo. Indeksi kreiraju "mapu puta" koja omogućava SQL Server-u da brzo pronađe podatke koje vam trebaju.
📊 Usporedba Performansi: Sa i Bez Indeksa
| Operacija | Bez Indeksa | Sa Indeksom |
|---|---|---|
| Traženje jednog reda | Table Scan (čita sve redove) ~1000ms za 1M redova |
Index Seek (direktno skok) ~1ms za 1M redova |
| Raspon vrijednosti | Table Scan ~1000ms |
Index Range Scan ~10ms |
| JOIN operacija | Nested Loop (sporo) ~5000ms |
Index Seek + Merge ~50ms |
| Sortiranje | Sort operator (sporo) ~2000ms |
Index Scan (već sortirano) ~20ms |
Zaključak: Indeksi mogu ubrzati upite 10-1000x! To je razlika između aplikacije koja radi i aplikacije koja je neupotrebljiva.
-- Without index: Table Scan (slow)
-- SQL Server reads every row to find EmployeeID = 5000
SELECT * FROM HR.Employees WHERE EmployeeID = 5000;
-- Estimated rows: 1,000,000
-- Estimated cost: Very high (full table scan)
-- With index: Index Seek (fast)
-- SQL Server uses index to jump directly to row 5000
SELECT * FROM HR.Employees WHERE EmployeeID = 5000;
-- Estimated rows: 1
-- Estimated cost: Very low (index seek)
🌲 B-Tree Internals: Pogled u mašinu
SQL Server koristi Balanced Tree strukturu. Ključna stvar je da je stablo uvijek balansirano, što znači da je svaki "list" (leaf node) na istoj udaljenosti od korijena (root node).
Anatomija 8KB Stranice Indeksa:
- Page Header (96 bytes): Čuva metadata (ID stranice, tip stranice, pointeri na lijevu/desnu stranicu).
- Data Rows: Ključevi po kojima sortiramo.
- Row Offset Array: Indeks na kraju stranice koji govori tačno gdje počinje koji red.
🔑 Koncept Uniquifier-a
Ako kreirate Clustered Index na koloni koja nije jedinstvena (npr. Prezime), SQL Server će tajno dodati 4-bajtni uniquifier svakom duplikatu. Ovo garantuje da svaki red u bazi ima unikatnu adresu.
🧱 Clustered vs Non-Clustered: Fundamentalna razlika
1. Clustered Index (Tabela je indeks)
Kada tabela ima Clustered Index, podaci Leaf Levela su zapravo stvarni redovi podataka. Nema pokazivača; tamo gdje završi pretraga u stablu, tamo leži vaš podatak.
2. Non-Clustered Index (Indeks na tabelu)
Ovaj indeks je odvojena datoteka. U svom Leaf Levelu, on sadrži ključ indeksa i pokazivač na Clustered Key (ili RID u slučaju Heapa).
⚡ Performance Insight
Kada radite pretragu preko Non-Clustered indeksa, ako tražite kolonu koja nije u indeksu, SQL Server mora uraditi Key Lookup (ići do Clustered indeksa da "pokupi" ostatak podataka). Ovo je skupo i cilj nam je to izbjeći.
🔍 Seek vs Scan: Odluka Optimizera
Query Optimizer donosi odluku na osnovu selektivnosti:
- Index Seek: Koristi B-Tree za brzi skok na određeni ključ. Koristi se kada tražimo mali procenat redova (< 1%).
- Index Scan: Čita cijeli indeks od početka do kraja. Koristi se kada je upit previše širok ili nema korisnog indeksa.
-- 1. Index Seek (Ultra brzo)
SELECT EmployeeID FROM HR.Employees WHERE EmployeeID = 500;
-- 2. Index Scan (Sporije na velikim tabelama)
-- Čak i ako koristimo PK, operacija 'LIKE' sa % na početku forsira SCAN
SELECT * FROM HR.Employees WHERE LastName LIKE '%asić';
🎯 Praktična Vježba: Istraživanje Strukture
Zadatak: "X-Ray" Tabele
Upotrijebit ćemo nedokumentovanu SQL naredbu DBCC IND da vidimo kako
SQL Server stvarno organizuje stranice vaše tabele.
Zadatak 1: Kreirajte tabelu InternalCheck sa Clustered indeksom.
Zadatak 2: Izvršite DBCC komandu i mapirajte Root, Intermediate i Leaf stranice.
-- Kreiranje baze i tabele
CREATE DATABASE ForensicDB;
GO
USE ForensicDB;
CREATE TABLE StorageTest (
ID INT PRIMARY KEY,
Data CHAR(2000) -- Veliki redovi da brže popunimo stranice
);
-- Napuni podatke
INSERT INTO StorageTest VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'D');
-- Pogledaj stranice (ID baze, ID tabele, IndexID)
-- IndexID 1 = Clustered Index
DBCC IND('ForensicDB', 'StorageTest', 1);
GO
-- NAPOMENA: U rezultatima tražite PageType:
-- 1 = Data Page (Leaf)
-- 2 = Index Page (Non-Leaf)
-- 10 = IAM Page (Metadata)
✅ Zaključak
Danas ste naučili kako SQL Server razmišlja:
- ✅ B-Tree nije samo teorija, to je fizički raspored na 8KB stranicama.
- ✅ Uniquifier omogućava klasterisanje na ne-unikatnim kolonama.
- ✅ Seek je hirurški precizan, dok je Scan brutalna sila.
- ✅ Key Lookup je skriveni ubica performansi koji ćemo riješiti u sljedećoj lekciji.
📚 Sljedeća Lekcija: Optimizacija
U Lekciji 2.2 učimo o Included Columns (kako eliminisati Key Lookup) i Filtered Indexes (kako smanjiti indeks za 90%).