0Pricing
SQL Academy · บทเรียน

การหลีกเลี่ยงลูปไม่รู้จบ

จำกัดความลึกและตรวจจับวงจร

การหลีกเลี่ยงลูปไม่รู้จบ เป็นบทเรียน SQL Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน

ปัญหาวงวนไม่สิ้นสุด

CTE แบบเรียกซ้ำมีประสิทธิภาพสูง แต่ก็มีความเสี่ยงร้ายแรง หากคำสั่งสอบถามไม่เคยไปถึงกรณีฐาน คำสั่งจะวนซ้ำตลอดไป ใช้หน่วยความจำที่มีอยู่ทั้งหมด และทำให้เซสชันฐานข้อมูลขัดข้อง

การทำความเข้าใจสาเหตุของวงวนไม่สิ้นสุดคือขั้นตอนแรกในการป้องกันปัญหา

เมื่อใดวงวนจึงไม่สิ้นสุด

CTE แบบเรียกซ้ำจะวนซ้ำไม่สิ้นสุดเมื่อส่วนแบบเรียกซ้ำยังคงสร้างแถวใหม่ โดยไม่เคยไปถึงสถานะที่ไม่มีการสร้างแถวใหม่อีก

เหตุการณ์นี้มักเกิดในสองกรณี: ไม่มีเงื่อนไขสิ้นสุดหรือเงื่อนไขไม่ถูกต้อง หรือข้อมูลมีวงจรที่โหนด A ชี้ไปยัง B และ B ชี้ย้อนกลับมายัง 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;

การเพิ่มขีดจำกัดความลึก

กลไกป้องกันที่ง่ายที่สุดคือตัวนับความลึก เพิ่มคอลัมน์ที่เพิ่มขึ้นทีละ 1 ในทุกขั้นตอนแบบเรียกซ้ำ แล้วหยุดเมื่อค่าเกินความลึกสูงสุด

วิธีนี้รับประกันการสิ้นสุดไม่ว่าข้อมูลจะเป็นอย่างไร และขีดจำกัดที่เลือกจะทำหน้าที่เป็นเพดานความปลอดภัย

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;

ขีดจำกัดความลึกในคำสั่งสอบถามลำดับชั้น

เมื่อเดินสำรวจลำดับชั้นพนักงาน คุณสามารถติดตามความลึกไปพร้อมกับเส้นทางได้ ส่วนคำสั่ง WHERE depth < 5 จะป้องกันไม่ให้เดินสำรวจเกิน 5 ระดับ แม้ข้อมูลจะมีลิงก์ที่ลึกกว่าหรือเป็นวงกลม

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;

การตรวจจับวงจรคืออะไร

วงจรเกิดขึ้นในข้อมูลกราฟเมื่อการติดตามเส้นเชื่อมไปเรื่อย ๆ แล้วกลับมายังโหนดที่เคยเยี่ยมชมแล้ว ตัวอย่างเช่น A → B → C → A

ขีดจำกัดความลึกยังคงทำให้คำสั่งสอบถามสิ้นสุดลงเมื่อข้อมูลมีวงจร แต่จะไม่บอกคุณว่า ตำแหน่งใดคือวงจร การตรวจจับวงจรโดยตรงทำได้

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;

ติดตามโหนดที่เยี่ยมชมแล้วด้วยอาร์เรย์

เทคนิคตรวจจับวงจรที่รัดกุมคือการส่งต่อ อาร์เรย์ของ ID โหนดที่เยี่ยมชมแล้วผ่านการเรียกซ้ำ ก่อนเยี่ยมชมโหนดถัดไป ให้ตรวจสอบว่าโหนดนั้นมีอยู่ในอาร์เรย์แล้วหรือไม่ หากมีอยู่แล้ว ให้ข้ามโหนดนั้น

PostgreSQL ทำให้เรื่องนี้ง่ายด้วยตัวดำเนินการ ANY(array) และตัวดำเนินการต่อท้ายอาร์เรย์ ||

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;

ส่วนคำสั่ง CYCLE (PostgreSQL 14+)

PostgreSQL 14 เพิ่มส่วนคำสั่ง CYCLE ในตัวสำหรับ CTE แบบเรียกซ้ำ ส่วนคำสั่งนี้จะเพิ่มสองคอลัมน์โดยอัตโนมัติ ได้แก่ ตัวบ่งชี้ค่าจริง/เท็จที่เป็น true เมื่อตรวจพบวงจร และอาร์เรย์ที่บันทึกเส้นทางที่เดินผ่าน

วิธีนี้เรียบง่ายกว่าการดูแลอาร์เรย์ด้วยตนเอง

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;

รวมขีดจำกัดความลึกและการตรวจจับวงจร

การใช้ทั้งขีดจำกัดความลึกและการตรวจจับวงจรร่วมกันให้การรับประกันด้านความปลอดภัยที่ดีที่สุด:

  • ขีดจำกัดความลึกทำหน้าที่เป็นเพดานตายตัวโดยไม่ขึ้นอยู่กับคุณภาพข้อมูล
  • การตรวจจับวงจรจะหยุดทันทีที่พบวงวน ช่วยประหยัดการวนซ้ำที่ไม่จำเป็น

ในคำสั่งสอบถามของระบบจริง ควรใช้กลไกป้องกันอย่างน้อยหนึ่งอย่างเสมอ

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;

การสร้างเส้นทางเต็มเป็นข้อความ

นอกจากการตรวจจับวงจรแล้ว การบันทึกเส้นทางการเดินสำรวจทั้งหมดเป็นข้อความที่มนุษย์อ่านเข้าใจได้ก็มีประโยชน์ การนำ ID ของโหนดมาต่อกันโดยคั่นด้วย -> ทำให้แสดงหรือแก้ไขข้อบกพร่องของเส้นทางที่เดินผ่านกราฟได้ง่าย

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;

การกำหนดจำนวนรอบการเรียกซ้ำสูงสุด

ฐานข้อมูลบางชนิด (MariaDB และ MySQL รุ่นเก่า) ใช้ตัวแปรของเซสชันเพื่อจำกัดการเรียกซ้ำ ใน PostgreSQL วิธีการที่เทียบเท่าคือใช้ตัวนับความลึกที่คุณเขียนขึ้นเอง หรือใช้ระยะหมดเวลาระดับคำสั่ง

การตั้งค่า statement_timeout เป็นกลไกป้องกันขั้นสุดท้ายที่จะยุติคำสั่งสอบถามที่ทำงานไม่หยุดหลังจากเวลาที่กำหนด

-- 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';

การเลือกขีดจำกัดความลึกที่เหมาะสม

ไม่มีขีดจำกัดความลึกที่ใช้ได้กับทุกกรณี ให้เลือกตามความลึกสูงสุดที่เป็นไปได้จริงในข้อมูลของคุณ:

  • ผังองค์กรแทบไม่เกิน 10–15 ระดับ — ใช้ depth < 20 เป็นเผื่อความปลอดภัยที่เหมาะสม
  • ต้นไม้ระบบไฟล์อาจลึกถึง 50–100 ระดับ
  • การเดินสำรวจกราฟเครือข่ายสังคมมักจำกัดไว้ที่การเชื่อมต่อ 3–6 ขั้น

ตั้งขีดจำกัดให้สูงพอที่จะครอบคลุมข้อมูลที่ถูกต้อง แต่ต่ำพอที่จะตรวจจับคำสั่งสอบถามที่ทำงานไม่หยุดได้ตั้งแต่เนิ่น ๆ

-- 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;

ขีดจำกัดความลึกเทียบกับการตรวจจับวงจร

คุณควรใช้วิธีใด

ทบทวน: การทำให้คำสั่งสอบถามแบบเรียกซ้ำปลอดภัย

ต่อไปนี้คือสรุปสิ่งที่คุณได้เรียนรู้เกี่ยวกับการหลีกเลี่ยงวงวนไม่สิ้นสุดใน CTE แบบเรียกซ้ำ:

  • ขีดจำกัดความลึก — เพิ่มคอลัมน์ตัวนับและหยุดด้วย WHERE depth < N มีประสิทธิภาพเสมอและนำไปใช้ได้ง่าย
  • การตรวจจับวงจรด้วยอาร์เรย์ — เก็บ ID ของโหนดที่เยี่ยมชมแล้วในอาร์เรย์ และข้ามโหนดที่มีอยู่ในอาร์เรย์ หยุดได้ตั้งแต่พบวงจรแรก
  • ส่วนคำสั่ง CYCLE (PostgreSQL 14+) — รูปแบบคำสั่งในตัวที่ทำให้การติดตามวงจรเป็นอัตโนมัติด้วยคอลัมน์ is_cycle และ path
  • ระยะหมดเวลาของคำสั่ง — กลไกป้องกันระดับฐานข้อมูลสำหรับคำสั่งสอบถามที่ทำงานไม่หยุด ไม่ใช่สิ่งทดแทนตรรกะที่ถูกต้อง
  • ใช้ทั้งสองวิธีร่วมกัน โดยใช้ขีดจำกัดความลึกและการตรวจจับวงจรในระบบจริงเพื่อการรับประกันที่ดีที่สุด

ด้วยเทคนิคเหล่านี้ คุณจะสามารถเดินสำรวจลำดับชั้นและกราฟได้อย่างมั่นใจโดยไม่เสี่ยงทำให้ฐานข้อมูลขัดข้อง

คำถามที่พบบ่อย

บทเรียน “การหลีกเลี่ยงลูปไม่รู้จบ” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การหลีกเลี่ยงลูปไม่รู้จบ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การหลีกเลี่ยงลูปไม่รู้จบ”

จำกัดความลึกและตรวจจับวงจร คุณปฏิบัติ SQL Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การหลีกเลี่ยงลูปไม่รู้จบ” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน SQL Academy นี้ได้ไหม

ได้ บทเรียน SQL Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทำงานของ CTE แบบเรียกซ้ำ
  2. การเดินผ่านต้นไม้หมวดหมู่
  3. การสร้างชุดและลำดับ
  4. การหลีกเลี่ยงลูปไม่รู้จบ
← กลับไปที่ SQL Academy