📖 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.
-- 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.
-- 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".
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.
- Snimite opterećenje (workload) koristeći SQL Profiler ili Extended Events.
- Uvezite workload u DTA.
- 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 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:
- ✅ Included Columns sprečavaju nepotrebne posjete tabeli.
- ✅ Filtered Indexes štede resurse ciljajući specifične datasetove.
- ✅ Usage Stats nam govore istinu o tome šta bazi stvarno treba.
📚 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.