> [!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: **Table Scan** (tabuľka bez klastrovaného indexu = halda), **Clustered Index Scan** (celá tabuľka cez klastrovaný index) alebo **Index Scan** (celý neklastrovaný index). Ikona ukazuje tabuľku so šípkou cez všetky riadky. ## Čo to znamená Sken = SQL Server prečíta **celý objekt** (alebo jeho veľkú časť) riadok po riadku a až potom filtruje. Pri malej tabuľke alebo keď naozaj treba skoro všetky riadky je to najlacnejšia cesta. Pri veľkej tabuľke, z ktorej treba pár riadkov, je to plytvanie. ### Do hĺbky - **Predicate** pri skene = filter, ktorý sa aplikuje na každý prečítaný riadok (skoro zadarmo pre CPU, ale I/O za celú tabuľku sa zaplatí). - **Number of Rows Read** vs. **Actual Number of Rows** – koľko riadkov sken prečítal a koľko z nich prešlo filtrom. Veľký rozdiel = chýbajúci index alebo [non-SARGable podmienka](script.php?id=89&s=non-sargable-podmienky-funkcia-na-stlpci-like-x-or). - **Ordered = True** – sken číta v poradí kľúča, lebo to niekto vyššie potrebuje (Merge Join, Stream Aggregate, ORDER BY). **False** – poradie je jedno (môže použiť rýchlejšie čítanie podľa alokácie). - Sken s **TOP** (alebo EXISTS) sa môže zastaviť skôr – *row goal*. Vtedy *Number of Rows Read* je malý, aj keď ikona hovorí „Scan“. - **Index Scan** na úzkom neklastrovanom indexe je často lacnejší ako Clustered Index Scan – číta menej stránok, lebo index obsahuje len pár stĺpcov. - Sken je jediný spôsob, keď podmienka nie je na prvom stĺpci kľúča indexu. ## Kedy je to v poriadku / kedy problém | V poriadku | Problém | |---|---| | malá tabuľka (stovky stránok) | veľká tabuľka a výsledok je pár riadkov | | dotaz potrebuje väčšinu riadkov (reporty, agregácie) | sken na vnútornej strane Nested Loops (Number of Executions > 1) | | columnstore sken v batch mode | *Number of Rows Read* ≫ *Actual Number of Rows* | | sken s TOP, ktorý skončí po pár riadkoch | `PAGEIOLATCH_*` čakania pri tomto dotaze ([čakania](waits.php#PAGEIOLATCH_SH)) | ## Ako si to vyskúšať ```sql USE PlanLab; SET STATISTICS IO ON; -- 1) Na Amount nie je index → Clustered Index Scan + Predicate SELECT OrderID, Amount FROM dbo.Orders WHERE Amount > 999.50; -- 2) TOP: sken sa zastaví po 10 riadkoch (pozri Number of Rows Read) SELECT TOP (10) OrderID, Amount FROM dbo.Orders; -- 3) Úzky neklastrovaný index stačí → Index Scan (menej stránok ako celá tabuľka) SELECT COUNT(*) FROM dbo.Orders WHERE AccountCode LIKE '%99'; -- 4) Pre porovnanie: index na Amount → seek CREATE INDEX IX_Lab_Orders_Amount ON dbo.Orders (Amount); SELECT OrderID, Amount FROM dbo.Orders WHERE Amount > 999.50; DROP INDEX IX_Lab_Orders_Amount ON dbo.Orders; ``` Spusti s **Include Actual Execution Plan** (Ctrl+M). Pri každom skene otvor F4 a porovnaj *Number of Rows Read* a *Actual Number of Rows*, v *Messages* logical reads. ## Čo s tým 1. Over, či sken **naozaj vadí**: koľko stránok má tabuľka, koľkokrát za minútu dotaz beží, koľko riadkov vracia. 2. Ak treba pár riadkov z veľkej tabuľky: **index** na stĺpce z WHERE (rovnosti prvé, rozsah posledný), ideálne s INCLUDE stĺpcami, ktoré dotaz vracia – inak príde [Key Lookup](script.php?id=70&s=key-lookup-a-rid-lookup-dohladanie-chybajucich-stlpcov). 3. Ak index existuje a aj tak je sken: podmienka nie je SARGable ([funkcia na stĺpci, LIKE '%x'](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) alebo [zlý odhad](script.php?id=84&s=zle-odhady-riadkov-odhad-vs-skutocnost-a-statistiky). 4. Pri reportoch, ktoré čítajú milióny riadkov, je riešením skôr [columnstore](script.php?id=81&s=columnstore-a-batch-mode-actual-execution-mode-segment-elimination) než ďalší rowstore index. ## Súvisiace - [Index Seek – seek vs. residual predicate](script.php?id=69&s=index-seek-seek-predicate-vs-residual-predicate) - [Non-SARGable podmienky](script.php?id=89&s=non-sargable-podmienky-funkcia-na-stlpci-like-x-or) - [Chýbajúce indexy – návrhy optimalizátora](script.php?id=28&s=chybajuce-indexy-navrhy-optimalizatora) - [Používanie indexov](script.php?id=27&s=pouzivanie-indexov-seeks-scans-lookups-updates) - [Čakanie PAGEIOLATCH_SH](waits.php#PAGEIOLATCH_SH) - [I/O latencia podľa súborov](script.php?id=23&s=i-o-latencia-podla-databazovych-suborov) ## 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/) _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._
Table Scan a Clustered Index Scan – čítanie celej tabuľky
Kedy je sken celej tabuľky alebo indexu v poriadku a kedy je to problém: Predicate, Number of Rows Read, Ordered, row goal pri TOP a ako sken nahradiť seekom.