> [!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: **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._
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).