Preskočiť na obsah
Návod

Spools – Table, Index, Row Count a Window Spool (eager vs. lazy)

Spool operátory v pláne: eager vs. lazy, rebinds a rewinds, Eager Index Spool ako signál chýbajúceho indexu, Row Count Spool pri NOT EXISTS, Window Spool a Halloween protection.

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 Eager Index Spool na vnútornej strane Nested Loops](uploads/2026/11/plan-o11.svg "Eager Index Spool = optimalizátor si sám stavia index, ktorý chýba")

V SSMS: **Table Spool**, **Index Spool**, **Row Count Spool** a **Window Spool**, s logickou operáciou **Eager Spool** alebo **Lazy Spool**. Ikona pripomína valec (pracovnú tabuľku) so šípkou späť.

## Čo to znamená

Spool je **dočasné odloženie riadkov** do pracovnej tabuľky v tempdb (alebo v pamäti), aby sa nemuseli počítať znova alebo aby sa oddelilo čítanie od zápisu.

- **Eager** – spool najprv prečíta **celý** vstup a až potom posiela riadky ďalej (blokujúci).
- **Lazy** – ukladá riadky postupne, ako prichádzajú, a pri opakovanom použití ich prehrá.

### Do hĺbky

- **Table Spool** – pracovná tabuľka bez indexu. Typicky na vnútornej strane Nested Loops, keď sa ten istý vstup opakuje (*rewind* = prehraj uložené, *rebind* = hodnota sa zmenila, prepočítaj). Vlastnosti **Actual Rebinds / Actual Rewinds** ukážu pomer.
- **Index Spool** – pracovná tabuľka **s indexom**. *Eager Index Spool* na vnútornej strane Nested Loops znamená: optimalizátor si pri **každom spustení dotazu** stavia dočasný index nad celým vstupom, lebo trvalý chýba. Je to jeden z najjasnejších signálov „sem patrí index“ (často bez zeleného návrhu Missing Index).
- **Row Count Spool** – uloží len **počet** riadkov; typicky pri `NOT EXISTS` / `NOT IN` na overenie, či vnútorný vstup niečo vrátil, bez opakovaného čítania.
- **Window Spool** – pre window funkcie s rámcom (`ROWS` / `RANGE`). Predvolený rámec `RANGE` pri `ORDER BY` používa pracovnú tabuľku na disku, `ROWS` pamäť – viď [Window funkcie v pláne](script.php?id=80&s=window-funkcie-v-plane-segment-sequence-project-a-window-spool).
- **Halloween protection** – v UPDATE/INSERT/DELETE plánoch Eager Table Spool oddelí čítanie od zápisu, aby sa ten istý riadok nespracoval dvakrát (napr. keď UPDATE mení stĺpec indexu, podľa ktorého sa číta). Viď [INSERT / UPDATE / DELETE v pláne](script.php?id=82&s=insert-update-a-delete-v-plane-narrow-vs-wide-plan-assert).
- Práca spoolu sa ukáže v STATISTICS IO ako **Worktable**.

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

| V poriadku | Problém |
|---|---|
| Lazy Table Spool s veľa rewinds a málo rebinds | Eager Index Spool nad veľkou tabuľkou (chýba index) |
| Row Count Spool pri NOT EXISTS | Table Spool s tisíckami rebinds (nič sa neušetrí) |
| Eager Spool pre Halloween protection pri malej zmene | Window Spool na disku pri veľkom počte riadkov |

## Ako si to vyskúšať

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

-- 1) Eager Index Spool: OrderLines.ProductID nemá index
SELECT p.ProductID, x.Qty
FROM dbo.Products AS p
CROSS APPLY (SELECT TOP (1) l.Qty FROM dbo.OrderLines AS l
             WHERE l.ProductID = p.ProductID ORDER BY l.Qty DESC) AS x;

-- 2) S trvalým indexom spool zmizne (porovnaj Worktable v Messages)
CREATE INDEX IX_Lab_Lines_Product ON dbo.OrderLines (ProductID, Qty);
SELECT p.ProductID, x.Qty
FROM dbo.Products AS p
CROSS APPLY (SELECT TOP (1) l.Qty FROM dbo.OrderLines AS l
             WHERE l.ProductID = p.ProductID ORDER BY l.Qty DESC) AS x;
DROP INDEX IX_Lab_Lines_Product ON dbo.OrderLines;

-- 3) Halloween protection: UPDATE stĺpca, podľa ktorého sa číta (v transakcii, potom ROLLBACK)
CREATE INDEX IX_Lab_Orders_Amount ON dbo.Orders (Amount);
BEGIN TRAN;
    UPDATE dbo.Orders SET Amount = Amount + 1 WHERE Amount < 12;
ROLLBACK;
DROP INDEX IX_Lab_Orders_Amount ON dbo.Orders;
```

Spusti s **Include Actual Execution Plan** (Ctrl+M). V bode 1 nájdi Index Spool (Eager) a v *Messages* riadok *Worktable*. V bode 3 hľadaj Table Spool (Eager) medzi čítaním a Index Update – ak ho optimalizátor nepoužil, zvolil iný spôsob čítania.

## Čo s tým

1. **Eager Index Spool** → vytvor trvalý index so stĺpcami zo *Seek Predicates* spoolu (a stĺpcami, ktoré vracia).
2. Table Spool s veľa rebinds → pozri, prečo sa vnútorný vstup opakovane prepočítava (korelovaný poddotaz, chýbajúci index).
3. Halloween spool pri veľkých UPDATE → zváž, či aktualizovať stĺpec indexu, podľa ktorého sa vyberá, alebo meniť dáta po dávkach.
4. Window Spool → `ROWS UNBOUNDED PRECEDING` namiesto predvoleného `RANGE`, kde to dáva rovnaký výsledok.

## Súvisiace

- [Nested Loops](script.php?id=71&s=nested-loops-vnorene-cykly-outer-a-inner-vstup)
- [Window funkcie v pláne](script.php?id=80&s=window-funkcie-v-plane-segment-sequence-project-a-window-spool)
- [INSERT / UPDATE / DELETE v pláne](script.php?id=82&s=insert-update-a-delete-v-plane-narrow-vs-wide-plan-assert)
- [Chýbajúci index v pláne](script.php?id=92&s=chybajuci-index-v-plane-zeleny-text-preco-ho-nebrat-naslepo)
- [Kto spotrebúva tempdb](script.php?id=20&s=kto-spotrebuva-tempdb)

## 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/)
- [Erik Darling – blog (plány, memory grants, parameter sniffing)](https://erikdarling.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._