Preskočiť na obsah
Návod

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.

4 min čítania 4 kroky 1 obrázok SQL Server 2016+
> [!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

![Index Seek v B-strome s Seek Predicates a residual Predicate](uploads/2026/11/plan-o2.svg "Seek skočí na začiatok rozsahu; Predicate filtruje až prečítané riadky")

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._