0Pricing
SQL Academy · درس

تجنّب الحلقات اللانهائية

حدود العمق واكتشاف الدورات

تجنّب الحلقات اللانهائية درس مجاني في SQL Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في SQL Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة SQL Academy 4 دروس في المجموع.

مشكلة الحلقة اللانهائية

تتمتع CTEs التكرارية بقدرات قوية، لكنها تنطوي على خطر كبير: إذا لم يصل الاستعلام أبدًا إلى حالة أساسية، فسيدور إلى ما لا نهاية، مستهلكًا كل الذاكرة المتاحة ومتسببًا في تعطل جلسة قاعدة البيانات.

يُعد فهم أسباب حدوث الحلقات اللانهائية الخطوة الأولى نحو منعها.

متى لا تنتهي الحلقة؟

يدور 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;

تتبّع العقد التي تمت زيارتها باستخدام مصفوفة

من الأساليب القوية لاكتشاف الدورات تمرير مصفوفة بمعرّفات العقد التي تمت زيارتها عبر التكرار. قبل زيارة العقدة التالية، تحقّقوا مما إذا كانت موجودة مسبقًا في المصفوفة. وإذا كانت موجودة، فتجاوزوها.

يسهّل 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 مضمّنة لـ CTEs التكرارية. وتضيف تلقائيًا عمودين: علامة منطقية تكون قيمتها 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;

إنشاء المسار الكامل كسلسلة نصية

إلى جانب اكتشاف الدورات، من المفيد تسجيل مسار الاجتياز الكامل كسلسلة نصية سهلة القراءة. إن وصل معرّفات العقد باستخدام -> يجعل عرض المسار الذي تم اجتيازه في الرسم البياني أو تصحيحه أمرًا سهلًا.

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;

تعيين max_recursive_iterations

تستخدم بعض قواعد البيانات، مثل 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;

حدود العمق في مقابل اكتشاف الدورات

أي تقنية ينبغي لكم استخدامها؟

مراجعة: الحفاظ على أمان الاستعلامات التكرارية

إليكم ملخصًا لما تعلّمتموه حول تجنب الحلقات اللانهائية في CTEs التكرارية:

  • حد العمق — أضيفوا عمود عدّاد وأوقفوا التنفيذ باستخدام WHERE depth < N. وهو فعّال دائمًا وسهل التطبيق.
  • اكتشاف الدورات باستخدام المصفوفات — مرّروا معرّفات العقد التي تمت زيارتها في مصفوفة وتجاوزوا أي عقدة موجودة فيها مسبقًا. يوقف التنفيذ مبكرًا عند أول دورة.
  • عبارة CYCLE‏ (PostgreSQL 14 فأحدث) — صياغة مضمّنة تؤتمت تتبّع الدورات باستخدام عمودي is_cycle وpath.
  • statement_timeout — شبكة أمان على مستوى قاعدة البيانات للاستعلامات الخارجة عن السيطرة، وليست بديلًا عن المنطق السليم.
  • اجمعوا بينهما، أي حد العمق واكتشاف الدورات، في بيئة الإنتاج للحصول على أقوى ضمان.

باستخدام هذه التقنيات، يمكنكم اجتياز التسلسلات الهرمية والرسوم البيانية بثقة من دون المخاطرة بتعطل قواعد البيانات.

الأسئلة الشائعة

هل درس «تجنّب الحلقات اللانهائية» مجاني؟

نعم — نص درس «تجنّب الحلقات اللانهائية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة SQL Academy، انتقل إلى CoddyKit PRO. تتضمن دورة SQL Academy 4 دروس في المجموع.

ماذا ستتعلم في «تجنّب الحلقات اللانهائية»؟

حدود العمق واكتشاف الدورات تتمرن على SQL Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ SQL Academy؟

لا تُشترط خبرة سابقة. SQL Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «تجنّب الحلقات اللانهائية»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس SQL Academy هذا؟

نعم. كل درس في SQL Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. كيف تعمل CTEs التكرارية
  2. استعراض شجرة الفئات
  3. إنشاء السلاسل والتسلسلات
  4. تجنّب الحلقات اللانهائية
← العودة إلى SQL Academy