SQL Academy · Lekcja

Unikanie nieskończonych pętli

Limity głębokości i wykrywanie cykli

Lekcja 4 z 413 kroki

Unikanie nieskończonych pętli to bezpłatna lekcja SQL Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej SQL Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs SQL Academy zawiera 4 lekcji w sumie.

Problem nieskończonej pętli

Rekurencyjne CTE są potężne, ale wiążą się z poważnym ryzykiem: jeśli zapytanie nigdy nie dotrze do przypadku bazowego, będzie wykonywać się bez końca, zużyje całą dostępną pamięć i spowoduje awarię sesji bazy danych.

Zrozumienie, dlaczego występują nieskończone pętle, to pierwszy krok do zapobiegania im.

Kiedy pętla nigdy się nie kończy?

Rekurencyjne CTE wykonuje się bez końca, gdy człon rekurencyjny wciąż generuje nowe wiersze i nigdy nie dochodzi do stanu, w którym nie są generowane nowe wiersze.

Zwykle dzieje się tak w dwóch sytuacjach: brakuje warunku zakończenia lub jest on nieprawidłowy albo dane zawierają cykl, w którym węzeł A wskazuje B, a B z powrotem A.

-- Simple recursive CTE that WOULD loop forever
-- (do NOT run this as-is; illustration only)
WITH RECURSIVE counter AS (
  SELECT 1 AS n          -- base case
  UNION ALL
  SELECT n + 1           -- recursive term
  FROM counter
  -- no WHERE clause to stop it!
)
SELECT n FROM counter;

Dodawanie limitu głębokości

Najprostszym zabezpieczeniem jest licznik głębokości. Należy dodać kolumnę zwiększaną o 1 w każdym kroku rekurencyjnym, a następnie zatrzymać zapytanie po przekroczeniu maksymalnej głębokości.

Gwarantuje to zakończenie niezależnie od danych, a wybrany limit stanowi bezpieczny górny pułap.

WITH RECURSIVE counter AS (
  SELECT 1 AS n
  UNION ALL
  SELECT n + 1
  FROM counter
  WHERE n < 10       -- stop at depth 10
)
SELECT n FROM counter;

Limit głębokości w zapytaniu hierarchicznym

Podczas przechodzenia przez hierarchię pracowników można śledzić głębokość razem ze ścieżką. Klauzula WHERE depth < 5 zapobiega przejściu dalej niż 5 poziomów, nawet jeśli dane zawierają głębsze lub cykliczne powiązania.

CREATE TEMP TABLE employees (
  id   INT PRIMARY KEY,
  name TEXT,
  manager_id INT
);

INSERT INTO employees VALUES
  (1, 'Alice', NULL),
  (2, 'Bob',   1),
  (3, 'Carol', 2),
  (4, 'Dave',  3);

WITH RECURSIVE hierarchy AS (
  SELECT id, name, manager_id, 1 AS depth
  FROM employees
  WHERE manager_id IS NULL          -- root

  UNION ALL

  SELECT e.id, e.name, e.manager_id, h.depth + 1
  FROM employees e
  JOIN hierarchy h ON e.manager_id = h.id
  WHERE h.depth < 5                 -- depth limit
)
SELECT id, name, depth FROM hierarchy ORDER BY depth, id;

Czym jest wykrywanie cykli?

Cykl występuje w danych grafowych, gdy podążanie krawędziami ostatecznie prowadzi z powrotem do już odwiedzonego węzła. Na przykład: A → B → C → A.

Limit głębokości nadal kończy zapytanie w przypadku danych zawierających cykl, ale nie wskazuje, gdzie znajduje się cykl. Jawne wykrywanie cykli pozwala to ustalić.

CREATE TEMP TABLE edges (
  from_node INT,
  to_node   INT
);

-- Introduce a cycle: 1->2->3->1
INSERT INTO edges VALUES
  (1, 2),
  (2, 3),
  (3, 1),   -- cycle back to 1
  (1, 4);   -- also a non-cyclic branch

SELECT * FROM edges;

Śledzenie odwiedzonych węzłów za pomocą tablicy

Skuteczną techniką wykrywania cykli jest przekazywanie przez rekurencję tablicy identyfikatorów odwiedzonych węzłów. Przed odwiedzeniem kolejnego węzła należy sprawdzić, czy znajduje się już w tablicy. Jeśli tak, należy go pominąć.

PostgreSQL ułatwia to dzięki operatorowi ANY(array) oraz operatorowi || do dodawania elementów do tablicy.

WITH RECURSIVE traverse AS (
  -- Start from node 1
  SELECT from_node,
         to_node,
         ARRAY[from_node] AS visited
  FROM edges
  WHERE from_node = 1

  UNION ALL

  SELECT e.from_node,
         e.to_node,
         t.visited || e.from_node
  FROM edges e
  JOIN traverse t ON e.from_node = t.to_node
  WHERE NOT (e.from_node = ANY(t.visited))   -- skip visited nodes
)
SELECT from_node, to_node, visited
FROM traverse;

Klauzula CYCLE (PostgreSQL 14+)

PostgreSQL 14 wprowadził wbudowaną klauzulę CYCLE dla rekurencyjnych CTE. Automatycznie dodaje ona dwie kolumny: flagę logiczną, która ma wartość true, gdy wykryto cykl, oraz tablicę rejestrującą przebytą ścieżkę.

Jest to bardziej przejrzyste niż ręczne utrzymywanie tablicy.

WITH RECURSIVE traverse AS (
  SELECT from_node, to_node
  FROM edges
  WHERE from_node = 1

  UNION ALL

  SELECT e.from_node, e.to_node
  FROM edges e
  JOIN traverse t ON e.from_node = t.to_node
)
CYCLE from_node SET is_cycle USING path
SELECT from_node, to_node, is_cycle, path
FROM traverse;

Łączenie limitu głębokości z wykrywaniem cykli

Jednoczesne użycie limitu głębokości i wykrywania cykli zapewnia najsilniejszą ochronę:

  • Limit głębokości działa jako twardy pułap niezależnie od jakości danych.
  • Wykrywanie cykli zatrzymuje zapytanie natychmiast po znalezieniu pętli, oszczędzając niepotrzebnych iteracji.

W zapytaniach produkcyjnych zawsze należy zastosować co najmniej jedno z tych zabezpieczeń.

WITH RECURSIVE traverse AS (
  SELECT from_node,
         to_node,
         1 AS depth,
         ARRAY[from_node] AS visited
  FROM edges
  WHERE from_node = 1

  UNION ALL

  SELECT e.from_node,
         e.to_node,
         t.depth + 1,
         t.visited || e.from_node
  FROM edges e
  JOIN traverse t ON e.from_node = t.to_node
  WHERE t.depth < 10                           -- depth limit
    AND NOT (e.from_node = ANY(t.visited))     -- cycle guard
)
SELECT from_node, to_node, depth, visited
FROM traverse;

Budowanie pełnej ścieżki jako tekstu

Oprócz wykrywania cykli warto zapisywać pełną ścieżkę przejścia jako czytelny dla człowieka tekst. Łączenie identyfikatorów węzłów za pomocą separatora -> ułatwia wyświetlanie trasy przejścia przez graf lub debugowanie jej.

WITH RECURSIVE traverse AS (
  SELECT from_node,
         to_node,
         1 AS depth,
         ARRAY[from_node] AS visited,
         from_node::TEXT AS path_str
  FROM edges
  WHERE from_node = 1

  UNION ALL

  SELECT e.from_node,
         e.to_node,
         t.depth + 1,
         t.visited || e.from_node,
         t.path_str || ' -> ' || e.from_node::TEXT
  FROM edges e
  JOIN traverse t ON e.from_node = t.to_node
  WHERE t.depth < 10
    AND NOT (e.from_node = ANY(t.visited))
)
SELECT from_node, to_node, path_str, depth
FROM traverse
ORDER BY depth;

Ustawianie max_recursive_iterations

Niektóre bazy danych (MariaDB, starszy MySQL) używają zmiennej sesji do ograniczania rekurencji. W PostgreSQL równoważnym podejściem jest korzystanie z samodzielnie napisanego licznika głębokości lub limitów czasu na poziomie instrukcji.

Ustawienie statement_timeout to zabezpieczenie ostatniej szansy, które kończy dowolne zapytanie wymykające się spod kontroli po określonym czasie.

-- PostgreSQL: set a statement timeout as a safety net
SET statement_timeout = '5s';

-- Now any query that runs longer than 5 seconds is cancelled
WITH RECURSIVE counter AS (
  SELECT 1 AS n
  UNION ALL
  SELECT n + 1 FROM counter WHERE n < 1000000
)
SELECT MAX(n) FROM counter;

-- Reset to default when done
SET statement_timeout = '0';

Wybór właściwego limitu głębokości

Nie istnieje uniwersalny limit głębokości. Należy wybrać go na podstawie maksymalnej realistycznej głębokości danych:

  • Schemat organizacyjny rzadko przekracza 10–15 poziomów — należy użyć depth < 20 jako bezpiecznego zapasu.
  • Drzewo systemu plików może mieć głębokość 50–100 poziomów.
  • Przechodzenie po grafie sieci społecznościowej jest często ograniczane do 3–6 kroków.

Należy ustawić limit wystarczająco wysoki, aby uwzględnić prawidłowe dane, ale jednocześnie na tyle niski, aby wcześnie wykrywać zapytania wymykające się spod kontroli.

-- Example: org chart with a generous but safe depth cap
WITH RECURSIVE org AS (
  SELECT id, name, manager_id, 1 AS depth
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  SELECT e.id, e.name, e.manager_id, o.depth + 1
  FROM employees e
  JOIN org o ON e.manager_id = o.id
  WHERE o.depth < 20    -- realistic upper bound for an org chart
)
SELECT id, name, depth
FROM org
ORDER BY depth, name;

Limity głębokości a wykrywanie cykli

Którą technikę należy zastosować?

Podsumowanie: zabezpieczanie zapytań rekurencyjnych

Oto podsumowanie zdobytej wiedzy o unikaniu nieskończonych pętli w rekurencyjnych CTE:

  • Limit głębokości — dodaj kolumnę licznika i zatrzymaj zapytanie za pomocą WHERE depth < N. Zawsze skuteczny i łatwy do zaimplementowania.
  • Wykrywanie cykli za pomocą tablicy — przechowuj identyfikatory odwiedzonych węzłów w tablicy i pomijaj każdy węzeł, który już się w niej znajduje. Zatrzymuje zapytanie przy pierwszym cyklu.
  • Klauzula CYCLE (PostgreSQL 14+) — wbudowana składnia automatyzująca śledzenie cykli za pomocą kolumn is_cycle i path.
  • statement_timeout — zabezpieczenie na poziomie bazy danych przed zapytaniami wymykającymi się spod kontroli, które nie zastępuje prawidłowej logiki.
  • Połącz oba — limit głębokości i wykrywanie cykli w środowisku produkcyjnym, aby uzyskać najsilniejszą gwarancję.

Dzięki tym technikom można bezpiecznie przechodzić przez hierarchie i grafy bez ryzyka awarii bazy danych.

Bezpłatny start

Ucz się SQL dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
46
Lekcje
183

Często zadawane pytania

Czy lekcja „Unikanie nieskończonych pętli” jest bezpłatna?

Tak — pełny tekst „Unikanie nieskończonych pętli” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu SQL Academy, przejdź na CoddyKit PRO. Kurs SQL Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Unikanie nieskończonych pętli”?

Limity głębokości i wykrywanie cykli Ćwiczysz SQL Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć SQL Academy?

Nie wymagamy żadnego doświadczenia. SQL Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Unikanie nieskończonych pętli”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji SQL Academy?

Tak. Każda lekcja SQL Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Jak działają rekurencyjne CTE
  2. Przechodzenie drzewa kategorii
  3. Generowanie serii i sekwencji
  4. Unikanie nieskończonych pętli
← Powrót do SQL Academy