> [!TIP] > Recept z [kuchárky exekučných plánov](script.php?id=64&s=exekucne-plany-kucharka-zacni-tu). Ukážky bežia v testovacej databáze [PlanLab](script.php?id=65&s=planlab-testovacia-databaza-pre-recepty-exekucnych-planov) – nikdy ich nespúšťaj na produkcii. ## Ako to vyzerá v pláne  V SSMS: **Index Seek** (neklastrovaný index) alebo **Clustered Index Seek**. Ikona ukazuje strom s cestou k jednému listu. ## Čo to znamená Index je zoradený strom (B-strom). **Seek** znamená, že SQL Server zíde od koreňa priamo na miesto, kde začínajú hľadané hodnoty, a číta len potrebný **rozsah** listov. Je to ako hľadať v telefónnom zozname podľa priezviska – neprechádzaš všetky strany. Pozor: „seek“ ešte neznamená „málo práce“. Rozhoduje, **aký veľký rozsah** sa číta a koľko z neho sa potom zahodí. ### Do hĺbky - **Seek Predicates** – podmienka, ktorou sa v indexe **hľadá** (určuje začiatok a koniec rozsahu). SSMS ju ukazuje ako *Prefix* (rovnosti na prvých stĺpcoch kľúča), *Start* a *End* (rozsah na ďalšom stĺpci). - **Predicate** (residual) – podmienka, ktorá sa testuje na **každom prečítanom** riadku rozsahu. Stĺpec v nej je v indexe, ale nedá sa podľa neho hľadať (nie je v kľúči na správnom mieste, je zabalený vo funkcii…). - **Number of Rows Read** (SQL Server 2016+) – koľko riadkov seek naozaj prečítal. Ak je ≫ *Actual Number of Rows*, väčšinu práce robil residual predicate. - Seek ide len po **prefixe kľúča**: index `(CustomerID, OrderDate)` vie hľadať podľa `CustomerID` alebo `CustomerID + OrderDate`, ale nie podľa samotného `OrderDate`. - Neunikátny neklastrovaný index má v kľúči skryto aj **kľúč klastrovaného indexu** (v PlanLab `OrderID`). Preto sa dá hľadať aj `CustomerID = 1 AND OrderID > 250000`. - „Seek, ktorý je v skutočnosti sken“: `WHERE CustomerID > 0` je formálne seek, ale prečíta celý index. - Viac seekov v jednom operátore: `IN (1, 2, 3)` alebo `OR` na tom istom stĺpci sa zmení na niekoľko rozsahov v jednom Index Seek. ## Kedy je to v poriadku / kedy problém | V poriadku | Problém | |---|---| | Rows Read ≈ Actual Rows | Rows Read ≫ Actual Rows (residual robí prácu) | | malý rozsah, málo vykonaní | seek na vnútornej strane Nested Loops s tisíckami vykonaní | | za seekom nie je Key Lookup alebo len pár | za seekom Key Lookup pre tisíce riadkov | | odhad ≈ skutočnosť | odhad 1, skutočnosť 100 000 ([zlé odhady](script.php?id=84&s=zle-odhady-riadkov-odhad-vs-skutocnost-a-statistiky)) | ## Ako si to vyskúšať ```sql USE PlanLab; SET STATISTICS IO ON; -- 1) Prefix + rozsah na skrytom kľúči OrderID → oboje v Seek Predicates SELECT OrderID FROM dbo.Orders WHERE CustomerID = 1 AND OrderID > 250000; -- 2) Rovnaký rozsah, ale výraz na OrderID → residual Predicate (Rows Read 100 000) SELECT OrderID FROM dbo.Orders WHERE CustomerID = 1 AND OrderID % 3 = 0; -- 3) Formálne seek, prakticky sken celého indexu SELECT COUNT(*) FROM dbo.Orders WHERE CustomerID > 0; -- 4) Zložený index: hľadanie podľa druhého stĺpca nejde → Index Scan CREATE INDEX IX_Lab_Cust_Date ON dbo.Orders (CustomerID, OrderDate); SELECT COUNT(*) FROM dbo.Orders WHERE CustomerID = 42 AND OrderDate >= '20250101'; -- seek: prefix + rozsah SELECT COUNT(*) FROM dbo.Orders WITH (INDEX (IX_Lab_Cust_Date)) WHERE OrderDate >= '20260101'; -- len 2. stĺpec → Index Scan DROP INDEX IX_Lab_Cust_Date ON dbo.Orders; ``` Spusti s **Include Actual Execution Plan** (Ctrl+M), pri každom Index Seek otvor F4 a porovnaj *Seek Predicates*, *Predicate* a *Number of Rows Read*. ## Čo s tým 1. Pri veľkom *Number of Rows Read*: dostaň stĺpec z *Predicate* do **kľúča** indexu na správne miesto (rovnosti prvé, rozsah ako posledný). 2. Ak je v Predicate funkcia alebo konverzia, prepíš podmienku na SARGable tvar ([non-SARGable](script.php?id=89&s=non-sargable-podmienky-funkcia-na-stlpci-like-x-or), [implicitná konverzia](script.php?id=88&s=implicitna-konverzia-convert-implicit-varchar-vs-nvarchar)). 3. Ak za seekom nasleduje [Key Lookup](script.php?id=70&s=key-lookup-a-rid-lookup-dohladanie-chybajucich-stlpcov), zváž INCLUDE stĺpce. 4. Neuspokoj sa so slovom „Seek“ – vždy pozri počty riadkov a vykonaní. ## Súvisiace - [Table Scan a Clustered Index Scan](script.php?id=68&s=table-scan-a-clustered-index-scan-citanie-celej-tabulky) - [Key Lookup a RID Lookup](script.php?id=70&s=key-lookup-a-rid-lookup-dohladanie-chybajucich-stlpcov) - [Non-SARGable podmienky](script.php?id=89&s=non-sargable-podmienky-funkcia-na-stlpci-like-x-or) - [Implicitná konverzia](script.php?id=88&s=implicitna-konverzia-convert-implicit-varchar-vs-nvarchar) - [Používanie indexov](script.php?id=27&s=pouzivanie-indexov-seeks-scans-lookups-updates) - [Chýbajúce indexy – návrhy optimalizátora](script.php?id=28&s=chybajuce-indexy-navrhy-optimalizatora) ## Zdroje - [Microsoft Learn – Showplan Logical and Physical Operators Reference](https://learn.microsoft.com/sql/relational-databases/showplan-logical-and-physical-operators-reference) - [Hugo Kornelis – SQL Server Execution Plan Reference (SQLServerFast)](https://sqlserverfast.com/epr/) - [Paul White – SQLkiwi (články o optimalizátore a operátoroch)](https://www.sql.kiwi/) - [Grant Fritchey – SQL Server Execution Plans, 3. vydanie (Redgate, e-kniha zadarmo)](https://www.red-gate.com/simple-talk/books/sql-server-execution-plans-third-edition/) _Texty sú vlastné, tvrdenia overené proti uvedeným zdrojom (11/2026). T-SQL je staticky skontrolovaný, nie spustený na živom serveri – vyskúšaj ho najprv v PlanLab._
Index Seek – seek predicate vs. residual predicate
Index Seek v pláne: Seek Predicates (Prefix, Start, End) vs. residual Predicate, Number of Rows Read, prefix kľúča indexu a prečo „seek“ ešte neznamená málo práce.