Preskočiť na obsah
Návod

Key Lookup a RID Lookup – dohľadanie chýbajúcich stĺpcov

Key Lookup a RID Lookup v pláne: prečo vznikajú, Output List, Number of Executions, bod zlomu medzi lookupom a skenom a ako ich odstrániť pokrývajúcim indexom (INCLUDE).

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

![Plán s Index Seek, Key Lookup a Nested Loops](uploads/2026/11/plan-o3.svg "Key Lookup beží raz pre každý riadok z indexu – Number of Executions je kľúčové číslo")

V SSMS: **Key Lookup (Clustered)** – vždy pod **Nested Loops**, vedľa Index Seek alebo Index Scan. Na tabuľke bez klastrovaného indexu (halda) je to **RID Lookup** (RID = identifikátor riadku: súbor, stránka, slot).

## Čo to znamená

Neklastrovaný index obsahuje len svoje stĺpce (+ kľúč klastrovaného indexu). Keď dotaz potrebuje ďalšie stĺpce, SQL Server pre **každý nájdený riadok** skočí do klastrovaného indexu (alebo haldy) a chýbajúce stĺpce dočíta. Pre 10 riadkov je to v poriadku. Pre 100 000 riadkov je to 100 000 samostatných skokov – často drahšie ako prečítať celú tabuľku.

### Do hĺbky

- **Output List** Key Lookupu = presne tie stĺpce, ktoré indexu chýbajú. Ak je tam aj stĺpec z WHERE, uvidíš ho v *Predicate* lookupu (filter až po dočítaní).
- **Number of Executions** = počet skokov. Každý stojí toľko logical reads, koľko úrovní má klastrovaný index (typicky 2–4).
- Optimalizátor volí medzi „seek + lookup“ a „sken celej tabuľky“ podľa **odhadu** riadkov. Bod zlomu (*tipping point*) býva pri malom percente riadkov tabuľky. Zlý odhad (napr. [parameter sniffing](script.php?id=87&s=parameter-sniffing-jeden-plan-pre-rozne-hodnoty-psp-2022-oppo-2025)) vedie k lookupu pre obrovský počet riadkov.
- RID Lookup na halde je o niečo lacnejší (priamy skok na stránku), ale haldy majú iné problémy (forwarded records).
- V XML plánu je Key Lookup obyčajný Clustered Index Seek s atribútom `Lookup="1"`.

## Kedy je to v poriadku / kedy problém

| V poriadku | Problém |
|---|---|
| desiatky až stovky vykonaní | tisíce a viac vykonaní |
| Output List má stĺpec, ktorý sa vracia zriedka | dotaz beží často a lookup má väčšinu nákladu |
| odhad ≈ skutočnosť | odhad 10, skutočnosť 100 000 |

## Ako si to vyskúšať

```sql
USE PlanLab;
SET STATISTICS IO ON;

-- 1) Malý zákazník: seek + pár lookupov (lacné)
SELECT OrderID, Amount, Note FROM dbo.Orders WHERE CustomerID = 42;

-- 2) Veľký zákazník: optimalizátor radšej zvolí sken celej tabuľky
SELECT OrderID, Amount, Note FROM dbo.Orders WHERE CustomerID = 1;

-- 3) Vynútený index (len na ukážku!): 100 000 lookupov – porovnaj logical reads s bodom 2
SELECT OrderID, Amount, Note FROM dbo.Orders WITH (INDEX (IX_Orders_CustomerID)) WHERE CustomerID = 1;

-- 4) Pokrývajúci index: lookup zmizne
CREATE INDEX IX_Lab_Orders_Cust_Cover ON dbo.Orders (CustomerID) INCLUDE (Amount, Note);
SELECT OrderID, Amount, Note FROM dbo.Orders WHERE CustomerID = 1;
DROP INDEX IX_Lab_Orders_Cust_Cover ON dbo.Orders;

-- 5) RID Lookup na halde
DROP TABLE IF EXISTS dbo.Lab_OrdersHeap;
SELECT * INTO dbo.Lab_OrdersHeap FROM dbo.Orders WHERE OrderID <= 50000;
CREATE INDEX IX_Lab_Heap_Cust ON dbo.Lab_OrdersHeap (CustomerID);
SELECT OrderID, Amount FROM dbo.Lab_OrdersHeap WHERE CustomerID = 42;
DROP TABLE dbo.Lab_OrdersHeap;
```

Spusti s **Include Actual Execution Plan** (Ctrl+M). Pri bode 3 pozri pri Key Lookup *Number of Executions* a v *Messages* logical reads (stovky tisíc oproti tisícom pri skene).

## Čo s tým

1. Pozri **Output List** – ktoré stĺpce chýbajú. Treba ich naozaj? `SELECT *` je častý dôvod.
2. Ak dotaz beží často: pridaj chýbajúce stĺpce do indexu ako **INCLUDE** (nie do kľúča – nezväčšuje to vnútorné úrovne stromu). Pri stĺpci z WHERE zváž kľúč.
3. Nerozširuj index o všetko: každý ďalší stĺpec spomalí INSERT/UPDATE a zväčší index. Pri širokých dotazoch je niekedy lepší sken.
4. Ak lookup vznikol kvôli zlému odhadu (plán pre malého zákazníka použitý pre veľkého), riešenie je v recepte [Parameter sniffing](script.php?id=87&s=parameter-sniffing-jeden-plan-pre-rozne-hodnoty-psp-2022-oppo-2025), nie v indexe.

## Súvisiace

- [Index Seek – seek vs. residual predicate](script.php?id=69&s=index-seek-seek-predicate-vs-residual-predicate)
- [Nested Loops](script.php?id=71&s=nested-loops-vnorene-cykly-outer-a-inner-vstup)
- [Parameter sniffing](script.php?id=87&s=parameter-sniffing-jeden-plan-pre-rozne-hodnoty-psp-2022-oppo-2025)
- [Používanie indexov (seeks, scans, lookups)](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/)
- [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/)
- [Brent Ozar – blog a nástroje First Responder Kit](https://www.brentozar.com/)

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