Preskočiť na obsah
Návod

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.

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 Clustered Index Scan a oknom Properties](uploads/2026/11/plan-o1.svg "Sken prečíta všetko a filtruje až potom – Number of Rows Read ukáže, koľko práce bolo zbytočnej")

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