MODUL 2 - LEKCIJA 2

Dizajniranje Optimizovanih Strategija

Pokrivanje Upita, Uključene Kolone i Filtrirani Indeksi za drastično ubrzanje upita

⏱️ Trajanje: ~3 časa 📚 Nivo: Napredni 🎯 Praktični primjeri: 6

📖 Od osnova do majstorstva

U prethodnoj lekciji smo vidjeli da Non-clustered indeksi pomažu u pronalaženju redova. Međutim, ako moramo "skakati" nazad u tabelu da bismo dobili vrijednosti drugih kolona (Key Lookup), performanse će i dalje ispaštati. U ovoj lekciji učimo kako napraviti Covering Indexes koji sadrže sve potrebne odgovore unutar samog indeksa.

📦 Included Columns: Eliminacija Key Lookup-a

Included Columns nam omogućavaju da dodamo ne-ključne kolone u Leaf Level Non-clustered indeksa. Ove kolone se ne koriste za sortiranje stabla, ali su prisutne kada engine stigne do kraja pretrage.

⚡ Zašto je ovo bitno?

Kada upit traži Email i Phone na osnovu LastName, indeks sa INCLUDE(Email, Phone) postaje Covering Index. SQL Server dobija sve odgovore iz indeksa i ne dotiče stvarnu tabelu. Ovo je često 10-100x brže.

🛠️ Kreiranje Covering Indeksa
-- Standardni indeks (može uzrokovati Key Lookup)
CREATE INDEX IX_Employees_LastName ON Stats.Employees (LastName);

-- Optimizovani indeks (Uključuje podatke bez dodatnog sortiranja)
CREATE INDEX IX_Employees_LastName_Covering 
ON Stats.Employees (LastName)
INCLUDE (Email, Position, Salary);
GO

🎯 Filtered Indexes: Precizno targetiranje

Zašto indeksirati milione redova za kolonu IsProcessed ako nas zanimaju samo neobrađeni redovi (kojih je možda 1%)? Filtered Index koristi WHERE klauzulu pri kreiranju indeksa.

💡 Prednosti:

  • Manji size: Indeks zauzima drastično manje prostora.
  • Brži update: SQL Server ne mora ažurirati indeks za redove koji ne odgovaraju filteru.
  • Preciznija statistika: Engine ima bolji uvid u podatke koji su vam stvarno bitni.
⚡ Primjer Filtered Indeksa
-- Indeks samo za draft izvještaje
CREATE NONCLUSTERED INDEX IX_Reports_Draft
ON Stats.Reports (CreatedDate)
WHERE Status = 'Draft';
GO

-- Ovaj indeks će biti korišten SAMO u upitima koji imaju isti filter u WHERE klauzuli.

📊 Održavanje i Index Usage Statistics

Gomilanje indeksa koje niko ne koristi je tihi ubica baze (usporava svaki INSERT/UPDATE). Kao developer, morate znati koji indeksi su "mrtav teret".

🔍 Pronalaženje Neiskorištenih Indeksa
SELECT 
    OBJECT_NAME(s.object_id) AS TableName,
    i.name AS IndexName,
    s.user_seeks, s.user_scans, s.user_lookups,
    s.user_updates AS WritesToMaintain
FROM sys.dm_db_index_usage_stats s
JOIN sys.indexes i ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE s.database_id = DB_ID()
AND OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1
AND s.user_seeks = 0 AND s.user_scans = 0; -- Nikad korišteni za čitanje

🧙‍♂️ Database Engine Tuning Advisor (DTA)

SQL Server ima ugrađenog "AI savjetnika" koji može analizirati vaše upite i predložiti idealnu strategiju indeksiranja.

  1. Snimite opterećenje (workload) koristeći SQL Profiler ili Extended Events.
  2. Uvezite workload u DTA.
  3. SQL Server će simulirati milione kombinacija indeksa i dati vam skriptu za implementaciju.

🎯 Praktična Vježba: Uništavanje Key Lookup-a

Zadatak: Analiza Plana Izvršenja

Upit SELECT Email, Position FROM Employees WHERE LastName = 'Hodžić' je spor uprkos indeksu na LastName.

Zadatak 1: Uključite "Actual Execution Plan" u SSMS (Ctrl+M).

Zadatak 2: Izvršite upit i potražite operaciju Key Lookup.

Zadatak 3: Kreirajte Covering Index koristeći INCLUDE i verifikujte nestanak Key Lookup-a.

💡 Rješenje
-- Rješenje koje transformiše Lookup u Seek
CREATE NONCLUSTERED INDEX IX_Employees_LastName_Email_Position
ON Stats.Employees (LastName)
INCLUDE (Email, Position);
GO

-- Nakon ovoga, plan će prikazati samo "Index Seek", bez dodatnih koraka.

✅ Zaključak

Današnja lekcija vas je uvela u svijet naprednog query tuninga:

📚 Sljedeća Lekcija

U Lekciji 2.3 prelazimo na potpuno drugačiju arhitekturu - Columnstore Indexes. Naučićete kako analizirati milijarde redova u milisekundama koristeći kolumnarno skladištenje.