Skip to main content
يعرض خطة تنفيذ عبارة.
الصيغة:
مثال:

أنواع EXPLAIN

  • AST — شجرة البنية النحوية المجردة.
  • SYNTAX — نص الاستعلام بعد التحسينات على مستوى AST.
  • QUERY TREE — شجرة الاستعلام بعد التحسينات على مستوى شجرة الاستعلام.
  • PLAN — خطة تنفيذ الاستعلام.
  • PIPELINE — مسار تنفيذ الاستعلام.
  • ANALYZE — ينفّذ الاستعلام ويُرفق بخطة التنفيذ مقاييس وقت التشغيل المقاسة.
  • ESTIMATE — العدد التقديري للصفوف والعلامات والأجزاء التي ستُقرأ من الجداول أثناء معالجة الاستعلام.
  • TABLE OVERRIDE — النتيجة التي تم التحقق من صحتها لتجاوز جدول على مخطط دالة الجدول.

EXPLAIN AST

يعرض AST للاستعلام. يدعم جميع أنواع الاستعلامات، وليس SELECT فقط. الإعدادات:
  • graph – يطبع AST على شكل رسم بياني موصوف بلغة وصف الرسوم البيانية DOT. القيمة الافتراضية: 0.
أمثلة:

EXPLAIN SYNTAX

يعرض شجرة البنية النحوية المجرّدة (AST) للاستعلام بعد تحليل البنية النحوية. يتم ذلك عبر تحليل الاستعلام، وبناء AST وشجرة الاستعلام، وتشغيل محلل الاستعلامات وتمريرات التحسين اختياريًا، ثم تحويل شجرة الاستعلام مرة أخرى إلى AST الخاصة بالاستعلام. الإعدادات:
  • oneline – يطبع الاستعلام في سطر واحد. القيمة الافتراضية: 0.
  • run_query_tree_passes – يشغّل تمريرات شجرة الاستعلام قبل إخراج شجرة الاستعلام. القيمة الافتراضية: 0.
  • query_tree_passes – إذا كان run_query_tree_passes مضبوطًا، فإنه يحدّد عدد التمريرات التي سيتم تشغيلها. ومن دون تحديد query_tree_passes، يتم تشغيل جميع التمريرات.
  • single_record – يعيد الاستعلام المعاد تنسيقه كسجل واحد متعدد الأسطر بدلًا من سجل واحد لكل سطر. القيمة الافتراضية: 1 (يتحكم فيه الإعداد explain_syntax_single_record). اضبطه على 0 لاستعادة الإخراج السابق بسجل واحد لكل سطر، أو اضبط explain_syntax_single_record = 0 (عموميًا أو ضمن SETTINGS لكل استعلام)، أو اضبط compatibility على أي إصدار أقدم من 26.8.
أمثلة:
Query
Response
باستخدام run_query_tree_passes:
Query
Response

EXPLAIN شجرة الاستعلام

الإعدادات:
  • run_passes — شغّل جميع تمريرات شجرة الاستعلام قبل إخراج شجرة الاستعلام. القيمة الافتراضية: 1.
  • dump_passes — أخرج معلومات عن التمريرات المستخدمة قبل إخراج شجرة الاستعلام. القيمة الافتراضية: 0.
  • passes — يحدّد عدد التمريرات المطلوب تشغيلها. إذا ضُبطت على -1، فسيُشغّل جميع التمريرات. القيمة الافتراضية: -1.
  • dump_tree — اعرض شجرة الاستعلام. القيمة الافتراضية: 1.
  • dump_ast — اعرض AST للاستعلام الناتجة من شجرة الاستعلام. القيمة الافتراضية: 0.
مثال:

EXPLAIN PLAN

يعرض خطوات خطة تنفيذ الاستعلام. الإعدادات:
  • optimize — يتحكم في ما إذا كانت تحسينات خطة تنفيذ الاستعلام تُطبَّق قبل عرض الخطة. القيمة الافتراضية: 1.
  • header — يطبع ترويسة المخرجات للخطوة. القيمة الافتراضية: 0.
  • description — يطبع وصف الخطوة. القيمة الافتراضية: 1.
  • indexes — يعرض الفهارس المستخدمة، وعدد الأجزاء التي تمت تصفيتها، وعدد الحبيبات التي تمت تصفيتها لكل فهرس مُطبَّق. القيمة الافتراضية: 0. مدعوم في جداول MergeTree. بدءًا من ClickHouse >= v25.9، لا تعرض هذه العبارة ناتجًا مناسبًا إلا عند استخدامها مع SETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0.
  • projections — يعرض جميع الإسقاطات التي جرى تحليلها وتأثيرها في التصفية على مستوى الأجزاء استنادًا إلى شروط المفتاح الأساسي للإسقاط. ولكل إسقاط، يتضمن هذا القسم إحصاءات مثل عدد الأجزاء والصفوف وعلامات ونطاقات التي جرى تقييمها باستخدام المفتاح الأساسي للإسقاط. كما يوضح عدد data parts التي تم تخطيها بسبب هذه التصفية، من دون القراءة من الإسقاط نفسه. ويمكن تحديد ما إذا كان الإسقاط قد استُخدم فعليًا للقراءة أو جرى تحليله فقط لأغراض التصفية من خلال الحقل description. القيمة الافتراضية: 0. مدعوم في جداول MergeTree.
  • actions — يطبع معلومات تفصيلية عن إجراءات الخطوة. القيمة الافتراضية: 1.
  • sorting — يطبع وصف الفرز لكل خطوة في الخطة تنتج مخرجات مرتبة. القيمة الافتراضية: 0.
  • keep_logical_steps — يحتفظ بالخطوات المنطقية في الخطة لعمليات joins بدلًا من تحويلها إلى تطبيقات join فعلية. القيمة الافتراضية: 0.
  • json — يطبع خطوات خطة تنفيذ الاستعلام كسطر بتنسيق JSON. القيمة الافتراضية: 0. يُنصح باستخدام تنسيق TabSeparatedRaw (TSVRaw) لتجنب عمليات إفلات غير الضرورية.
  • input_headers — يطبع ترويسات الإدخال للخطوة. القيمة الافتراضية: 0. يفيد غالبًا المطورين فقط في استكشاف المشكلات المتعلقة بعدم تطابق ترويسات الإدخال والإخراج.
  • column_structure — يطبع أيضًا بنية الأعمدة في الترويسات بالإضافة إلى اسمها ونوعها. القيمة الافتراضية: 0. يفيد غالبًا المطورين فقط في استكشاف المشكلات المتعلقة بعدم تطابق ترويسات الإدخال والإخراج.
  • distributed — يعرض خطط الاستعلام المنفَّذة على العقد البعيدة للجداول الموزعة أو parallel replicas. غير مدعوم مع json. القيمة الافتراضية: 0.
  • compact — عند التمكين، يُخفي خطوات expression ومعلومات الإجراءات التفصيلية (المدخلات، والدوال، والأسماء المستعارة، ومواضع المخرجات) من الخطة. ولا يكون له تأثير إلا عندما تكون actions = 1. القيمة الافتراضية: 1.
  • pretty — يطبع شجرة الخطة باستخدام محارف رسم الخطوط (├──, └──, │) بدلًا من المسافات البادئة لتوضيح التدرج الهرمي. كما ينسّق خصائص خطوة join ضمن السطر نفسه. القيمة الافتراضية: 1.
افتراضيًا، تكون قيمة explain_query_plan_default = 'pretty'، لذلك تُهيَّأ actions وcompact وpretty إلى 1، وتُعرَض الخطة بصيغة مضغوطة وجميلة ومشروحة بالإجراءات. ويؤدي تحديد أي من هذه الخيارات صراحةً في عبارة EXPLAIN (على سبيل المثال، EXPLAIN actions = 0, compact = 0, pretty = 0 SELECT ...) دائمًا إلى تجاوز القيمة الافتراضية.قبل ClickHouse 26.7، كانت القيم الافتراضية لكل من actions وcompact وpretty هي 0. ولا يزال بإمكانك الحصول على ذلك الناتج من خلال ضبط explain_query_plan_default = 'legacy' (على مستوى عام أو ضمن SETTINGS الخاصة بكل استعلام)، أو عن طريق ضبط compatibility على أي إصدار أقدم من 26.7.لا يؤدي الخياران json وdistributed إلى تفعيل إعدادات pretty الافتراضية (actions وcompact وpretty)، حتى عندما تكون قيمة explain_query_plan_default = 'pretty'. ولتضمين تفاصيل الإجراءات في مخرجاتهما، اضبط actions = 1 يدويًا.
مثال:
لا تتوفر إمكانية تقدير تكلفة الخطوة والاستعلام.
عند ضبط json = 1، تُمثَّل خطة تنفيذ الاستعلام بصيغة JSON. كل عقدة هي قاموس يحتوي دائمًا على المفاتيح Node Type وNode Id وPlans. تكون Node Type سلسلة نصية تتضمن اسم الخطوة، ويكون Node Id معرّفًا فريدًا للخطوة (اسم الخطوة مع لاحقة رقمية، مثل Union_10). أما Plans فهي مصفوفة من أوصاف الخطوات الفرعية. وقد تُضاف مفاتيح اختيارية أخرى بحسب نوع العقدة والإعدادات. مثال:
عند ضبط description = 1، يُضاف المفتاح Description إلى الخطوة:
عند ضبط header = 1، يُضاف المفتاح Header إلى الخطوة على هيئة مصفوفة من الأعمدة. مثال:
مع indexes = 1، يُضاف المفتاح Indexes. ويحتوي على مصفوفة من الفهارس المستخدمة. يُوصَف كل فهرس بتنسيق JSON مع المفتاح Type (سلسلة نصية بقيمة Partition Min-Max أو Partition أو Statistics أو PrimaryKey أو Skip) والمفاتيح الاختيارية التالية: يستخدم فهرس Statistics إحصاءات الأعمدة لكل جزء بيانات (القيم الدنيا/العليا، وعدد قيم NULL للأعمدة من نوع Nullable) لتخطي أجزاء البيانات التي لا يمكن أن تطابق عامل تصفية الاستعلام.
  • Name — اسم الفهرس (يُستخدم حاليًا فقط مع فهارس Skip).
  • Keys — مصفوفة الأعمدة التي يستخدمها الفهرس.
  • Condition — الشرط المستخدم.
  • Description — وصف الفهرس (يُستخدم حاليًا فقط مع فهارس Skip).
  • Parts — عدد الأجزاء بعد/قبل تطبيق الفهرس.
  • Granules — عدد الحبيبات بعد/قبل تطبيق الفهرس.
  • Ranges — عدد نطاقات الحبيبات بعد تطبيق الفهرس.
مثال:
عند تعيين projections = 1، يُضاف مفتاح Projections، ويحتوي على مصفوفة من الإسقاطات المُحللة. يُوصف كل إسقاط بصيغة JSON وفق المفاتيح التالية:
  • Name — اسم الإسقاط.
  • Condition — شرط المفتاح الأساسي المستخدم في الإسقاط.
  • Description — وصف كيفية استخدام الإسقاط (على سبيل المثال، التصفية على مستوى الجزء).
  • Selected Parts — عدد الأجزاء التي اختارها الإسقاط.
  • Selected Marks — عدد العلامات المختارة.
  • Selected Ranges — عدد النطاقات المختارة.
  • Selected Rows — عدد الصفوف المختارة.
  • Filtered Parts — عدد الأجزاء التي جرى تخطيها بسبب التصفية على مستوى الجزء.
مثال:
مع actions = 1، تعتمد المفاتيح المضافة على نوع الخطوة. مثال:
مع compact = 0 وactions = 1، يمكن رؤية خطوات Expression مع معلومات مفصلة عن التعبيرات:
عند تعيين distributed = 1، يتضمن الناتج ليس فقط خطة الاستعلام المحلية، بل أيضاً خطط الاستعلام التي ستُنفَّذ على العقد البعيدة. يُفيد هذا في تحليل الاستعلامات الموزعة وتصحيح أخطائها.
لا يُعرَض distributed إلا بصيغة legacy (غير pretty)، لأن مخرجات pretty لا تدمج خطط المقاطع البعيدة في شجرة الخطة. ولهذا السبب، يؤدي تمكين distributed تلقائيًا إلى تعطيل الإعدادات الافتراضية لـpretty (actions وcompact وpretty)، بغضّ النظر عن explain_query_plan_default. ولا يزال بإمكانك تعيين actions=1 يدويًا. كما أن الخيار distributed غير مدعوم أيضًا مع json.
مثال مع جدول موزّع:
مثال مع النسخ المتماثلة المتوازية:
في كلا المثالَين، تُظهر خطةُ الاستعلام تدفق التنفيذ الكامل بما في ذلك الخطوات المحلية والبعيدة. عند تعيين pretty = 1، تُعرض شجرة الخطة باستخدام أحرف رسم الخطوط بدلاً من المسافات البادئة، وتظهر معلومات إضافية للخطوات الرئيسية:
  • تُطبَع أعمدة ناتج الاستعلام في أعلى الخطة.
  • تُعرَض التعبيرات في عوامل التصفية، ومفاتيح التجميع، وأوصاف الفرز، ودوال النوافذ بصياغة مقروءة للبشر شبيهة بـ SQL (مثلًا، a + 1 > 5 بدلًا من greater(plus(a, 1), 5)). وتُزال بادئات معرّفات الأعمدة الداخلية (مثل __table1.) لتوضيح العرض.
  • تعرض خطوات المصدر (مثل ReadFromMergeTree) أعمدة ناتجها.
  • تعرض خطوات التصفية شرط التصفية بصياغة SQL. وعند وجود عوامل تصفية للربط وقت التشغيل، تُعرض بشكل منفصل.
  • تعرض خطوات التجميع المفاتيح والدوال التجميعية مع وسيطاتها (مثل sum(c) وcount()).
  • تُظهر مجموعات IN من قيم tuple الحرفية قيمها (مقتطعةً للمجموعات الكبيرة)، وتُوسَم المجموعات المستندة إلى استعلامات فرعية على أنها subquery1 وsubquery2 وما إلى ذلك، وتُظهر المجموعات من جداول محرك Set اسم الجدول.
  • تعرض خطوات الربط علاقة الربط باستخدام ترميز رياضي، والتقديرات التي أنتجها مُحسِّن ترتيب الربط للخطوة (التكلفة، والانتقائية، وصفوف الناتج، والصفوف لكل جانب)، وأعمدة الإدخال لكل جانب. وتُستخدم الرموز التالية لتمثيل أنواع الربط المختلفة:
على سبيل المثال، تعني t1 ⟕ t2 ربطًا أيسر بين الجدولين t1 وt2. يشير الرقم بين قوسين بعد اسم الجدول (مثلًا، t1[100]) إلى العدد التقديري للصفوف عندما تكون إحصاءات الجدول متاحة. أسفل علاقة الربط، تطبع كل خطوة ربط التقديرات التي أنتجها مُحسِّن ترتيب الربط لها:
  • Cost — تكلفة شجرة الربط الفرعية بالكامل ضمن هذه الخطوة، وهي القيمة التي يقللها المُحسِّن عند مقارنة ترتيبات الربط المرشحة. تكلفة الربط هي العدد المقدّر لأزواج الصفوف المتطابقة، <selectivity> * <left_rows> * <right_rows>، بالإضافة إلى تكلفة مدخلاته.
  • Selectivity — النسبة المقدّرة من حاصل الضرب الديكارتي للجانبين التي تبقى بعد تطبيق شرط الربط. تُشتق من عدد القيم المميزة (NDV) لمفاتيح الربط: يحتفظ تساوي المفتاح بنحو 1 / max(NDV_left, NDV_right) من الأزواج، وتُستخدم أصغر نسبة عبر شروط الربط.
  • Output rows — العدد المقدّر للصفوف التي ينتجها الربط: <selectivity> * <left_rows> * <right_rows> للربط الداخلي، وبحد أدنى <left_rows> لـLEFT، و<right_rows> لـRIGHT، و<left_rows> + <right_rows> لـFULL، لأن الربط الخارجي يحتفظ بكل صف من جانبه المحفوظ. عندما يشارك ربط SEMI أو ANTI في إعادة الترتيب، يُقدَّر باعتباره نسبة من جانبه المحفوظ، <preserved_rows> * min(1, <selectivity> * <other_rows>) لـSEMI، والصفوف المتبقية لـANTI؛ وإلا فإنه يستخدم معادلة نوع الربط الخاص به.
  • Left / Right — العدد المقدّر للصفوف الداخلة إلى الربط من كل جانب.
تُبلَّغ القيمة التي لم يتمكن المُحسِّن من تقديرها على أنها no stats. يحدث ذلك عندما لا يُشغَّل تحسين ترتيب الربط — مثلًا، عندما يكون query_plan_optimize_join_order_limit هو 0 — أو عندما لا يوجد أساس للتقدير. يسرد السطران Input (left): وInput (right): الأعمدة التي يُدخلها كل جانب إلى الربط. يعمل خيار pretty جيدًا مع compact = 1، الذي يُخفي خطوات Expression ومعلومات الإجراءات التفصيلية، مما يجعل الخطة أسهل قراءةً. مثال تفصيلي يتضمن عمليات ربط. يُخفَّض إعداد join_runtime_filter_min_probe_rows فقط لكي ينشئ جدول بهذا الحجم الصغير عامل تصفية للربط وقت التشغيل:

EXPLAIN PIPELINE

الإعدادات:
  • header — يطبع الترويسة لكل منفذ إخراج. القيمة الافتراضية: 0.
  • graph — يطبع رسمًا بيانيًا موصوفًا بلغة وصف الرسوم البيانية DOT. القيمة الافتراضية: 0.
  • compact — يطبع الرسم البياني في الوضع المضغوط إذا كان إعداد graph مفعّلًا. القيمة الافتراضية: 1.
  • compact_repeated_processor_chains — يضغط سلاسل المعالجات المتكررة والمتجاورة في المخرجات النصية، وذلك بعرض نسخة واحدة من السلسلة مع عدد مرات تكرارها. يمكن أن يجعل ذلك خطوط المعالجة المتوازية أسهل قراءةً عندما تتكرر السلسلة نفسها مرات عديدة، على سبيل المثال في عمليات JOIN. ولا يؤثر ذلك في مخرجات الرسم البياني. القيمة الافتراضية: 0.
عندما تكون compact=0 وgraph=1، ستتضمن أسماء المعالجات لاحقة إضافية تحتوي على معرّف فريد للمعالج. مثال:

EXPLAIN ANALYZE

يقوم EXPLAIN ANALYZE فعليًا بتشغيل الاستعلام، ثم يتجاهل صفوف النتائج ويطبع شجرة الخطة نفسها كما في EXPLAIN PLAN، مع إرفاق كل خطوة بتفاصيل عمّا حدث فعليًا أثناء التنفيذ. الإعدادات: يقبل EXPLAIN ANALYZE خيارات العرض نفسها التي يقبلها EXPLAIN PLAN (وهي موثقة في قسم EXPLAIN PLAN).
  • header — راجع قسم EXPLAIN PLAN.
  • description — راجع قسم EXPLAIN PLAN.
  • projections — راجع قسم EXPLAIN PLAN.
  • sorting — راجع قسم EXPLAIN PLAN.
  • input_headers — راجع قسم EXPLAIN PLAN.
  • column_structure — راجع قسم EXPLAIN PLAN.
  • actions — راجع قسم EXPLAIN PLAN. القيمة الافتراضية: 1.
  • indexes — راجع قسم EXPLAIN PLAN. القيمة الافتراضية: 1.
  • compact — راجع قسم EXPLAIN PLAN. القيمة الافتراضية: 1.
  • pretty — راجع قسم EXPLAIN PLAN. القيمة الافتراضية: 1.
  • processors — في EXPLAIN ANALYZE، يطبع سطرًا إضافيًا لكل مرحلة يتضمن توزيع الزمن المنقضي لكل معالج: min وmedian وmax وsum. ويفيد ذلك في اكتشاف اختلال توزيع الحمل بين المعالجات المتوازية. القيمة الافتراضية: 0.
  • matches — في EXPLAIN ANALYZE، يجعل خطوات الربط تُجري أعمال التتبع الإضافية اللازمة لمقاييس matched وmatch rate وfanout في الحالات التي لا يمكن فيها اشتقاق هذه الأرقام من ناتج الربط نفسه. وحيثما أمكن اشتقاقها، يُبلّغ عنها دون هذا الخيار. راجع خطوات الربط. القيمة الافتراضية: 0.
  • time — في EXPLAIN ANALYZE، يطبع سطرَي Time وConcurrency لكل خطوة، مع زمن الساعة الفعلي ومستوى التزامن للخطوة وفرعها. راجع زمن الساعة الفعلي ومستويات التزامن للخطوات والفروع. القيمة الافتراضية: 1.
لأن EXPLAIN ANALYZE ينفّذ الاستعلام المغلّف فعليًا، فهو يتصرف مثل ذلك الاستعلام — وعلى خلاف صيغ EXPLAIN التي لا تنفّذ شيئًا — بعدة طرق:
  • الحصص والحدود. يُحتسب ضمن الحصص ويخضع لنفس الحدود (مثل query_selects وread_rows) كما لو كنت تشغّل الاستعلام مباشرةً. ولا تُحتسب المصادر المعفاة من الحصص أثناء التخطيط (مثل system.one).
  • المعاملات الفاشلة. داخل معاملة سبق أن فشلت (ROLLED_BACK)، يُرفَض مع INVALID_TRANSACTION, تمامًا مثل SELECT عادي — أصدر ROLLBACK أولًا.
  • القراءات المتدفقة. عند إجراء قراءة متدفقة (FROM ... STREAM) يُرفَض مع NOT_IMPLEMENTED، لأن هذا النوع من القراءة لا يكتمل أبدًا.
  • الاستعلامات الموزعة. هذا غير مدعوم للاستعلامات التي تُنفَّذ في وضع Distributed.
مثال:
لنفحص الناتج. أولاً، لننظر إلى الترويسة.
  • Time — الوقت الإجمالي، مقسَّماً إلى مرحلتَي التخطيط (أي إنشاء الخطة + تحسين الخطة + بناء مسار تنفيذ الاستعلام) والتنفيذ (تشغيل مسار تنفيذ الاستعلام).
  • Execution — لا يُعرَض عندما تكون time = 0. يقسّم وقت التنفيذ إلى in steps وoutside steps وidle. ويبلغ مجموع هذه الأجزاء قيمة execution. راجع زمن الساعة الفعلي ودرجات التوازي للخطوات والفروع.
  • Read — الصفوف والبايتات غير المضغوطة المقروءة من الجداول، مع معدل النقل — وهي الأرقام نفسها التي يَعرضها تذييل الاستعلام العادي تحت اسم “Processed”.
  • Peak memory — ذروة الذاكرة التي استخدمها الاستعلام.
والآن لنلقِ نظرة على الأسطر الجديدة التي تظهر في خطة تنفيذ الاستعلام.
يُبلَّغ عن الصفوف والبايتات مرة واحدة للخطوة بأكملها (سطر I/O). ويُبلَّغ عن الوقت ودرجة التوازي لكل مرحلة من مراحل الخطوة في السطر أو الأسطر التالية ذات المسافة البادئة.
  • rows <in> → <out> — الصفوف التي دخلت الخطوة وخرجت منها؛ وتُظهر (<selectivity>%) مقدار ما قامت الخطوة بتصفيته (out/in) أو توسيعه للبيانات. ويُخفى هذا عندما يتساوى عدد صفوف الإدخال مع عدد صفوف الإخراج، وعندما يساوي عدد صفوف الإدخال 0.
  • <bytes_in> → <bytes_out> — بايتات غير مضغوطة داخل الذاكرة تمر عبر الخطوة (يُحذف هذا إذا كانت القيمتان صفراً).
  • time <t> (<share>%) — زمن الساعة الفعلي الذي كانت المرحلة نشطة خلاله، ونسبتها من وقت تنفيذ الاستعلام (أي من دون وقت البناء). لاحظ أن مجموع النسب قد يتجاوز 100% لأن المراحل والخطوات تعمل بالتوازي.
  • parallelism <avg>/<max> — متوسط عدد خيوط CPU العاملة داخل هذه المرحلة في الوقت نفسه، من أصل الحد الأقصى الذي يمكنها استخدامه. يقتصر هذا المقياس على المرحلة: فلا تُحتسب خيوط التنفيذ التي تعمل على خطوات أخرى من الاستعلام في الوقت نفسه. وتشير قيمة قريبة من الحد الأقصى إلى أن المرحلة استُخدم فيها التوازي بكفاءة؛ أما القيمة القريبة من 1 فتعني أنها نُفِّذت في الغالب بشكل تسلسلي.
  • Stage (<stage>) — اسم المرحلة. في الخطوة التي تحتوي على مرحلة واحدة، يُعرَض سطر الوقت مباشرةً من دون تسمية Stage (...). أما الخطوات التي تضم عدة مراحل، فتطبع سطراً معنَّوناً لكل مرحلة؛ فعلى سبيل المثال، يعرض Aggregating السطرين Stage (partial aggregation) وStage (final aggregation)، ويعرض hash join السطرين Stage (build) وStage (probe).
لا يقتصر ClickHouse على موازاة تنفيذ المهام داخل خطوة في الخطة، بل يوازي أيضاً تنفيذ خطوات الخطة نفسها. يعكس مقياس parallelism عمل هذه الخطوة فقط. وقد تعمل خطوات أخرى بالتوازي، لذلك لا يوضح هذا الرقم كيف تقارن درجة التوازي في هذه الخطوة بالاستعلام ككل. وللاطلاع على هذا المنظور للاستعلام بأكمله، راجع مقياس Concurrency المعروض مع time = 1 (زمن الساعة الفعلي لخطوات الخطة وفروعها ومستويات التزامن).
يُحسَب الرقم الأقصى في parallelism بوصفه الحد الأدنى بين:
  1. العدد الإجمالي للمهام داخل خطوة الخطة؛
  2. الحد الأقصى لعدد خيوط معالجة الاستعلام المحدَّد في max_threads.

خطوات الربط

في خطوة ربط، يطبع EXPLAIN ANALYZE أسطرًا تقارن تقديرات محسّن ترتيب الربط بما حدث فعليًا (انظر مقاييس الربط المقدّرة مقابل الفعلية)، وأسطر المشاركة لكل جانب — Left وRight — ثم أي أسطر خاصة بتنفيذ الربط. يقابل Left وRight الجانبين المنطقيين في عبارة SQL. وفي معظم الحالات، يكون Left أيضًا جانب الفحص (probe) في الربط، وRight جانب البناء (build). غير أن هذا لا ينطبق دائمًا، إذ قد يحدث تبديل بين الجانبين أثناء تنفيذ الربط. تُغطّى جميع قيم join_algorithm (hash، parallel_hash، grace_hash، partial_merge، full_sorting_merge، parallel_full_sorting_merge، direct)، وكذلك التنفيذات الثلاثة التي لا يمكن للإعداد اختيارها: ربط CROSS أو COMMA، وربط الحلقة المتداخلة على مستوى الكتل الذي يستخدم قسم ON لا يحدد أي مفتاح ربط، ومحرك الجدول Join. يُبلّغ معظمها عن كلا الجانبين، بينما يُبلّغ بعضها فقط عن الجانب الذي يجسّده ماديًا (على سبيل المثال، يطبع direct ‏Left: فقط). تشترك الأسطر الخاصة بكل جانب في البنية نفسها:
لكل جانب، يعرض EXPLAIN ANALYZE ما يلي:
  • rows estimated <estimated_rows> — تقدير محسّن ترتيب الربط لعدد صفوف ذلك الجانب، ويُطبع للمقارنة مع قيمة rows الفعلية المجاورة له؛ وتظهر no stats عندما لا ينتج المحسّن أي تقدير (انظر مقاييس الربط المقدّرة مقابل الفعلية).
  • rows <rows> — العدد الإجمالي لصفوف ذلك الجانب التي مرّت عبر عملية الربط.
  • matched <matched_rows> — عدد صفوف ذلك الجانب التي وجدت شريك ربط واحدًا على الأقل في الجانب الآخر. يحسب هذا الصفوف لا المفاتيح: إذا ظهر مفتاح ثلاث مرات في الجانب الأيمن وطابق، تُحسب الصفوف الثلاثة في الجانب الأيمن كلها كصفوف متطابقة.
  • match rate <match_rate>% — النسبة المئوية لصفوف ذلك الجانب التي تطابقت، وتُحسب كالتالي: 100 * <matched_rows> / <rows>.
  • fanout <fanout> — عدد صفوف الإخراج التي ينتجها، في المتوسط، كل صف متطابق من ذلك الجانب.
يُعرض الرقم الذي لا يمكن اشتقاقه بدقة على أنه not collected بدلًا من 0. يُشتق كل من match rate وfanout من matched، لذا يعرض الجانب الذي لا تتوفر لديه قيمة matched القيم الثلاث كلها باعتبارها not collected.

مقاييس ربط التقديرية والفعلية

تتضمن خطوة ربط تقديرات محسّن ترتيب الربط — وهي نفسها التي يعرضها EXPLAIN PLAN (راجع قسم EXPLAIN PLAN) — ويطبع EXPLAIN ANALYZE كلًا منها إلى جانب القيمة المقاسة:
  • Cost — كلتا القيمتين تعدّان صفوف الإخراج المطابقة. التقدير هو التكلفة التي يحسبها المُحسِّن لشجرة الـ ربط الفرعية: <selectivity> * <left_rows> * <right_rows> مضافًا إليها تكلفة مدخلاتها. أما القيمة الفعلية فتُقاس بالطريقة نفسها — صفوف الإخراج المطابقة لهذا الـ ربط مضافًا إليها التكلفة الفعلية لكل ربط أسفله ينتمي إلى عنقود إعادة الترتيب نفسه.
  • Selectivity — يُشتق التقدير من عدد القيم المميزة لمفاتيح الـ ربط؛ أما القيمة الفعلية فهي النسبة المقيسة من الجداء الديكارتي التي وصلت إلى الإخراج: <matched output rows> / (<left rows> * <right rows>)، إذ إن هذا هو ما يحاول المُحسِّن تقديره.
  • Output rows — العدد المقدَّر والعدد الفعلي للصفوف التي أنتجها الـ ربط. عندما تكون كلتا القيمتين غير صفرية، يُبلّغ q-error عن max(estimated / actual, actual / estimated)، وهو المقياس المعياري لجودة تقدير العدّية: القيمة 1.00 تعني تقديرًا مثاليًا، والقيمة الكبيرة تعني أن المُحسِّن اختار ترتيب الـ ربط اعتمادًا على عدّية خاطئة بدرجة كبيرة.
أما التقدير الذي لم يُجرَ أصلًا فيُبلَّغ عنه بـ no stats — على سبيل المثال، عندما لا يُشغَّل تحسين ترتيب الـ ربط لأن قيمة query_plan_optimize_join_order_limit هي 0. ويختلف هذا عن not collected، الذي يدل على قيمة فعلية لم يتمكن التنفيذ من قياسها. أما عمليات الـ ربط ذات الجانب الأيمن المملوء مسبقًا (محرك الجدول Join، وعمليات direct ربط) فلا تمرّ عبر محسّن ترتيب الربط وتطبع أسطر المشاركة فقط. كما تطبع خطوة الـ ربط سطري Input (left): وInput (right): مع الأعمدة التي يمرّرها كل جانب إلى الـ ربط. وبالنسبة لجداول مثال الـ ربط في EXPLAIN PLAN، تبدو خطوة الـ ربط في EXPLAIN ANALYZE SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id على النحو التالي:

Fanout

يقيس fanout مدى تضاعف الصفوف:
يُنتج الربط الخارجي صف إخراج واحدًا مُبطّنًا بـ NULL لكل صف في الجانب المحفوظ لم يجد صفًا مطابقًا. وتُطرح هذه الصفوف حتى لا تؤثر سلبًا في النسبة. ولا توجد إلا في جانب محفوظ: الجانب الأيمن في RIGHT وFULL، والجانب الأيسر في LEFT وFULL:
  • fanout = 0 — لم تُنتج الصفوف المتطابقة أي صف إخراج مطلقًا، وهذا ما يفعله ربط ANTI: إذ لا يُنتج إلا الصفوف التي لم تجد صفًا مطابقًا.
  • fanout = 1 — ربط نظيف بنسبة 1:1؛ إذ أنتج كل صف متطابق صف إخراج واحدًا بالضبط.
  • fanout > 1 — ربط بنسبة 1:N؛ إذ ضاعفت المفاتيح المكررة في الجانب الآخر عدد الصفوف. وتشير قيمة كبيرة في كلا الجانبين في الوقت نفسه إلى تضخم ديكارتي غير مقصود.

عندما تتطلب الأرقام matches = 1

تنتج معظم هذه الأرقام تلقائيًا عن البيانات التي ينشئها الربط، ويعرضها EXPLAIN ANALYZE العادي. أما البقية فتتطلب تتبعًا إضافيًا لا يجريه الربط لولا ذلك، لذا لا تُعرض إلا مع EXPLAIN ANALYZE matches = 1. ويعتمد ذلك على الخوارزمية؛ ففي عائلة التجزئة توجد حالتان:
  • الجانب الأيمن من ALL INNER وALL LEFT، إذ يتطلب الأمر وضع علامة على كل صف أيمن متطابق؛
  • الجانب الأيسر من ALL LEFT وALL FULL، ولكن فقط عندما لا يختار الاستعلام شيئًا من الجدول الأيمن ويكون قسم ON مساواة بسيطة للمفاتيح. بخلاف ذلك، يسجل الفحص بالفعل الصفوف اليسرى المتطابقة — إما لتجسيد الأعمدة اليمنى أو لتقييم الشرط المتبقي — ويكون العدد دقيقًا دون هذا الخيار.
تحتاج partial_merge إليه للجانب الأيمن من أنواع ALL الأربعة، للسبب نفسه. وتحتاج full_sorting_merge وparallel_full_sorting_merge إليه لكلا الجانبين في أنواع ANY. أما أنواع ALL فلا تحتاج إليه.
يكون الخيار معطّلًا افتراضيًا لأن التتبع الإضافي ليس بلا تكلفة، وتتمثل تكلفته في دقة القياس. يُنفَّذ العمل داخل حلقة الفحص ويزداد مع عدد صفوف الإخراج. استخدم matches = 1 عندما تحتاج إلى معرفة التطابقات الدقيقة التي وجدتها الصفوف اليسرى واليمنى في الجانب المقابل.
لا يجعل matches = 1 كل تركيبة قابلة للجمع. فالجانب الذي يستطيع الربط الإبلاغ عنه يتحدد بما يتعين على ذلك الربط تنفيذه على أي حال، لذا يعتمد على الخوارزمية، وكذلك على النوع والصرامة. عائلة التجزئة. تتفق hash وparallel_hash وgrace_hash دائمًا فيما بينها: يكون الجانب الأيمن غير متاح كلما احتفظ الربط بصف واحد فقط لكل مفتاح في جدول التجزئة، كما تفعل عمليات الربط ANY وSEMI وANTI: إذ لا تُخزَّن الصفوف اليمنى المكررة أصلًا، فلا يمكن عدّها. ويكون الجانب الأيسر غير متاح عندما يمنع الربط إخراج صف أيسر سبق أن استحوذ صف أيسر آخر على صفه المقابل، مما يجعل الصفوف الناتجة أقل من الصفوف المتطابقة. يحوّل تمكين any_join_distinct_right_table_keys ANY إلى دلالات RightAny الأقدم، التي تُنتج صفًا واحدًا لكل صف أيسر، ومن ثم تحافظ على كلا العددين. عندها يُبلّغ ANY RIGHT وANY FULL عن كلا الجانبين، ويُعاد كتابة ANY INNER إلى SEMI LEFT. يتبع محرك الجدول Join الجدول نفسه، باستخدام النوع والصرامة المعلنين في المحرك: يُبلّغ Join(ALL, INNER, …) عن كلا الجانبين، بينما لا يُبلّغ Join(ANY, LEFT, …) عن أي منهما. خوارزميات الدمج. تقبل full_sorting_merge وparallel_full_sorting_merge أنواع ALL الأربعة، وANY INNER وANY LEFT وANY RIGHT وASOF وASOF LEFT. وتُبلّغان عن كلا الجانبين لكل نوع باستثناء ASOF وASOF LEFT، حيث يكون الجانب الأيمن not collected، ومن دون الحاجة إلى matches = 1 — إذ تمرّان على الإدخالين المرتبين وتريان كل صف ضمن نطاق متساوٍ أثناء معالجته، لذا لا حاجة إلى إعادة بناء أي شيء لاحقًا. تقبل partial_merge كلاً من ALL INNER وALL LEFT وALL RIGHT وALL FULL وANY INNER وANY LEFT وSEMI LEFT. وتُبلّغ عن كلا الجانبين لأنواع ALL الأربعة، لكن الجانب الأيمن يتطلب matches = 1؛ أما في ANY INNER وANY LEFT وSEMI LEFT فالجانب الأيمن هو not collected. direct. الجانب الأيسر فقط. فالجانب الأيمن مخزن مفاتيح-قيم لا يُجسَّد أبدًا في صورة صفوف، لذا لا يحتوي على سطر Right: مطلقًا. CROSS وCOMMA وON ثابت. لا يُجمع أي من الجانبين، كما هو موضح أعلاه. ربط الحلقة المتداخلة على مستوى الكتل. هو المشغّل الذي يتولى قسم ON الذي لا يحدد أي مفتاح ربط، ما لم يستحوذ أحد المسارات السابقة له على الشرط (راجع allow_block_nested_loop_join). ويقبل هذا المشغّل جميع الأنواع ودرجات الصرامة باستثناء ASOF وPASTE وANY FULL: يُبلَّغ عن الجانب الأيسر دون الحاجة إلى الخيار حيثما كان المشغّل يسجّل أصلًا الصفوف اليسرى التي تطابقت: فكل نوع يحتفظ بالصفوف اليسرى غير المتطابقة يحتاج إلى هذا السجل، وكذلك كل درجة صرامة تأخذ زوجًا واحدًا على الأكثر لكل صف أيسر. ويُبلَّغ عن الجانب الأيمن حيثما احتفظ المشغّل بعلامات التطابق، وهي ما يحتاج إليه RIGHT وFULL وSEMI/ANTI الموجَّهان من اليمين. ويحتفظ ANY INNER بهذه العلامات أيضًا، لكن لغرض واحد فقط هو إسناد صف أيمن إلى صف أيسر واحد: إذ يكفّ عن الاستحواذ لصالح أي صف أيسر حصل على زوجه بالفعل، فتدل العلامات على أزواج النتيجة لا على كل تطابق، ولذلك لا يُبلَّغ عنها. أما ALL INNER فلا يصل إلى هذا المشغّل إلا عند تعطيل hash، وإلا تحوّل الشرط إلى CROSS JOIN مع عامل تصفية. يؤدي تفعيل any_join_distinct_right_table_keys إلى تحويل ANY إلى دلالات RightAny هنا أيضًا، وهي دلالات تُبلِّغ عن الجانب الأيسر في جميع الأنواع وعن الجانب الأيمن في RIGHT وFULL؛ كما يُعاد كتابة ANY INNER بصيغة SEMI LEFT. وحيثما أبلغت خوارزميتان كلتاهما عن رقم، جاء الرقمان متطابقين. فكل ما في الأمر أن خوارزميات الدمج تمتلك معلومات أكثر، لكنها لا تختلف في تحديد ما يُعدّ تطابقًا.

الأسطر الخاصة بالخوارزمية

لنلقِ نظرة على الأسطر الإضافية التي يضيفها كل تنفيذ لعملية الربط. بالنسبة إلى عمليتَي الربط hash وparallel_hash، ولمحرك الجدول Join، يصف السطر Hash table: جدول التجزئة المُنشأ من الجدول الأيمن:
  • unique keys <unique_keys> — عدد المفاتيح الفريدة المخزّنة في جدول التجزئة أثناء مرحلة البناء.
  • memory <peak_memory> — ذروة الذاكرة التي استخدمها جدول التجزئة أثناء مرحلة البناء.
بالنسبة إلى ربط grace_hash، يعرض السطر Hash table: أيضًا كيفية تكيّف الربط مع حد الذاكرة، بينما يبيّن السطر Spill: ما إذا كانت البيانات قد نُقلت إلى القرص:
  • buckets <buckets> — عدد الحاويات التي استقر عليها ربط grace hash join عند انتهاء التنفيذ. يكون هذا العدد دائمًا قوة للعدد 2.
  • rehashes <rehashes> — عدد المرات التي تعيّن فيها مضاعفة عدد الحاويات ليتناسب مع حد الذاكرة.
  • Spill: — راية yes/no تشير إلى ما إذا حدثت أي عملية ترحيل إلى القرص. عند حدوث ذلك، يُظهر left spilled <left_spilled_bytes> وright spilled <right_spilled_bytes> عدد البايتات المضغوطة المُرحَّلة من الجانبين الأيسر (probe) والأيمن (build)؛ وإذا لم يُرحَّل شيء، يكون السطر ببساطة Spill: no.
بالنسبة إلى ربط partial_merge، يحمل السطر Right: معلومات إضافية حول كيفية تخزين الجدول الأيمن مؤقتًا وفرزه، ويُعرض وقت الفرز في السطرين Stage (build) وStage (probe):
  • size <right_size> — حجم الذاكرة الذي تشغله كتل الجدول الأيمن.
  • blocks <right_blocks> — عدد الكتل التي خُزِّن فيها الجدول الأيمن مؤقتًا.
  • storage <in-memory|external> — ما إذا كان الجدول الأيمن يتسع في الذاكرة (in-memory) أو كان يجب ترحيله إلى القرص (external). عندما تكون القيمة external، يعرض الحقل الإضافي spilled <spilled_bytes> عدد البايتات المضغوطة المكتوبة على القرص.
  • sort time <sort_time> — الوقت المستغرق في فرز الجدول الأيمن (في مرحلة البناء) وكل كتلة واردة من الجانب الأيسر (في مرحلة الفحص).
  • sort share <sort_share>% — قيمة sort time كنسبة من الوقت النشط لتلك المرحلة نفسها (مجموع الوقت المنقضي لمعالجاتها)، بخلاف النسبة المئوية لـ time الخاصة بالمرحلة، التي تمثل حصة من وقت تنفيذ الاستعلام بأكمله.
بالنسبة إلى ربط full_sorting_merge، لا تُطبع سوى السطور المشتركة Left: وRight:. بالنسبة إلى ربط direct، لا يُطبع سوى السطر Left:، لأن الجانب الأيمن مخزن مفتاح-قيمة يُبحث فيه مباشرةً بدلًا من تجسيده في صفوف. بالنسبة إلى ربط CROSS أو COMMA، ولأي ربط ALL INNER JOIN لا يتضمن قسم ON فيه مساواة مفتاح — إذ يحوّله المخطِّط إلى CROSS JOIN ويستخدم ذلك الشرط عاملَ تصفية — يصف السطر Buffer: كيفية الاحتفاظ بالجدول الأيمن في الذاكرة، ويعرض السطر Spill: ما إذا كان قد رُحِّل إلى القرص:
  • memory <peak_memory> — ذروة الذاكرة التي استهلكها الجدول الأيمن المُخزَّن مؤقتًا.
  • compressed <yes|no> — ما إذا كانت كتلة واحدة مخزَّنة مؤقتًا على الأقل قد ضُغطت؛ وفي هذه الحالة تفكّ القارئات ضغط كل كتلة مخزَّنة.
  • Spill: — علامة yes/no نفسها المستخدمة مع grace_hash، ويعرض right spilled <right_spilled_bytes> عدد البايتات المضغوطة المكتوبة على القرص.
يعرض كلا الجانبين هنا matched not collected: إذ إن المسند الثابت إما يقرن كل صف من الجدول الأيسر بكل صف من الجدول الأيمن، أو لا يقرن أي صف منها، لذا لا يمكن تحديد الصفوف الفردية التي تطابقت. تطبع عملية ربط الحلقة المتداخلة على مستوى الكتل (block nested loop join)، التي تتولى جميع أقسام ON الأخرى الخالية من شرط تساوي المفاتيح، السطرين نفسيهما عن الجدول الأيمن الذي تُجسِّده:
  • memory <peak_memory> — ذروة الذاكرة التي شغلها الجدول الأيمن المخزّن. وتكون قيمتها 0 عندما تُكتب كل كتلة إلى القرص فور وصولها، إذ لم يُحتفظ بأيٍّ منها في الذاكرة قط.
  • compressed <yes|no> — يشير إلى ما إذا كانت كتلة مخزّنة واحدة على الأقل مضغوطة؛ وفي هذه الحالة يفك القارئ ضغط جميع الكتل المخزّنة.
  • Spill: — العلامة yes/no نفسها المستخدمة مع grace_hash، ويُبلّغ right spilled <right_spilled_bytes> عن عدد البايتات المضغوطة المكتوبة إلى القرص.
خلافًا لحالة CROSS، يمكن لكلا الجانبين هنا الإبلاغ عن matched، إذ يقيّم المشغّل شرطًا فعليًا على كل زوج يفحصه؛ ويوضح الجدول أعلاه أيّ الجانبين يفعل ذلك. في عملية ربط مع محرك الجدول Join، يُبلّغ عن كلا الجانبين، إلى جانب السطر Hash table: الذي يصف الجدول المُنشأ مسبقًا. ويعرض الجانب الأيمن عدد الصفوف المخزنة في المحرك، لا صفوف عملية بناء خاصة بكل استعلام.

الأوقات لكل معالِج

عند استخدام processors = 1، يُطبَع سطر إضافي أسفل كل مرحلة يوضح توزيع الزمن المنقضي عبر معالجات المرحلة:
يشير <n> إلى عدد المعالِجات في هذه المرحلة. ويشير الفارق الكبير بين median وmax إلى اختلال في توازن الحمل بين المعالِجات المتوازية.

زمن التنفيذ الفعلي ومستويات التزامن للخطوات والفروع

ما لم تكن time = 0، يُطبع سطران إضافيان لكل خطوة من خطوات خطة الاستعلام:
هنا، يشير step إلى الخطوة نفسها، بينما يشير branch إلى الشجرة الفرعية المتجذّرة فيها: أي الخطوة وجميع الخطوات التي تندرج تحتها في خطة التنفيذ.
  • Time — الوقت المنقضي الذي عمل خلاله خيط تنفيذ واحد على الأقل على الخطوة (step)، أو على أي خطوة في الشجرة الفرعية (branch). وتمثل <share> هذا الوقت كنسبة مئوية من وقت تنفيذ الاستعلام.
  • Concurrency — متوسط عدد خيوط التنفيذ التي كانت تعمل على أي خطوة من خطوات الاستعلام، لا على هذه الخطوة وحدها، أثناء نشاط الخطوة (step) أو شجرتها الفرعية (branch). وتمثل <m> الحد الأقصى لعدد خيوط التنفيذ التي يمكن أن يستخدمها الاستعلام. تعرض الخطوات التي ليس لها أي معالجات، مثل Union عندما تكون لمدخلاته الترويسة نفسها بالفعل، القيمة Unknown لـstep، إذ لا يوجد عمل يمكن قياسه. ويظل branch معروضًا.
يُشتق كلا المقياسين من فترات العمل. ففي كل مرة ينهي فيها خيط تنفيذ جزءًا من العمل، يسجّل المنفّذ فترة تشمل وقت البدء، ووقت الانتهاء، والخطوة التي ينتمي إليها العمل. قيمة Time للخطوة هي مدة اتحاد فترات عملها. ولا تُحتسب الفجوات التي لم يعمل خلالها أي خيط تنفيذ على الخطوة. أما قيمة Time للفرع فتُحسب بعد جمع فترات عمل جميع خطوات الشجرة الفرعية في اتحاد واحد. يمثل branch للخطوة الجذرية خطة التنفيذ كاملة، لكن حصته تقل عادةً عن 100%. ويوضح سطر Execution في ملخص الاستعلام أين استُهلك ما تبقى من وقت التنفيذ (انظر ملخص الاستعلام في المثال أعلاه):
  • in steps — اتحاد فترات عمل جميع خطوات خطة التنفيذ. وهو وقت branch للخطوة الجذرية، وحصته هي النسبة نفسها.
  • outside steps — الوقت الذي شغّل فيه خيط تنفيذ واحد على الأقل معالجًا، دون أن ينتمي أيٌّ من هذه المعالجات إلى خطوة في خطة التنفيذ هذه: مكوّن استقبال المخرجات، وResize، وعمليات التحويل، وخطوات الخطط التي لا يمكن الوصول إليها انطلاقًا من هذه الخطة.
  • idle — الوقت الذي لم يشغّل فيه أي خيط تنفيذ أي معالج: أثناء بدء تشغيل المنفّذ وإيقافه، وفي اللحظات التي كانت فيها جميع خيوط التنفيذ تنتظر.
تستخدم حصص Time، وحصص time لكل مرحلة، وحصص Execution المقام نفسه، لذا يمكن مقارنتها بعضها ببعض. تُحسب قيمة Concurrency بقسمة إجمالي الوقت النشط لجميع خيوط تنفيذ الاستعلام ضمن ذلك الاتحاد على مدته. وهي تحتسب خيوط التنفيذ التي تعمل على أي خطوة من خطوات الاستعلام، بخلاف مقياس parallelism لكل مرحلة، الذي يحتسب خيوط تنفيذ تلك المرحلة وحدها. وهي تجيب عن السؤال: “أثناء نشاط هذه الخطوة (أو الفرع)، إلى أي مدى كانت خيوط تنفيذ الاستعلام مشغولة إجمالًا؟” تعني القيمة القريبة من <m> أن الاستعلام ظل يعمل بأقصى درجة من التوازي طوال فترة نشاط تلك الخطوة. وتعني القيمة القريبة من 1 أن الاستعلام كان يعمل تسلسليًا خلال تلك الفترة: ففي حالة step، كان خيط التنفيذ المشغول الوحيد يعمل على الخطوة نفسها، ما يجعلها نقطة تنفيذ تسلسلي؛ أما في حالة branch، فكان الخيط يعمل على خطوة ما ضمن الشجرة الفرعية، وليس بالضرورة على هذه الخطوة. ولا تشكّل الخطوة التي تُنفّذ تسلسليًا عنق زجاجة فعليًا إلا إذا كانت حصتها من Time كبيرة أيضًا.

EXPLAIN ESTIMATE

يعرض العدد التقديري للصفوف والعلامات والأجزاء التي ستُقرأ من الجداول عند معالجة الاستعلام. يعمل مع الجداول من عائلة MergeTree. مثال إنشاء جدول:
Query
Query
Response

EXPLAIN WHATIF

يقدّر الفائدة التي قد يحققها فهرس تخطٍّ افتراضي لاستعلام SELECT، من دون تجسيد الفهرس على القرص. عرّف فهرسًا مرشحًا واحدًا أو أكثر باستخدام CREATE HYPOTHETICAL INDEX، ثم شغّل EXPLAIN WHATIF SELECT ... للاطلاع على مدى انطباق كل فهرس مرشح، والعدد المقدّر للعلامات المقروءة، والحجم المقدّر بالبايت، ونسبة التخطي. تُعدّ الإسقاطات الافتراضية المعرّفة باستخدام CREATE HYPOTHETICAL PROJECTION مرشحةً أيضًا. بالنسبة إلى الإسقاط العادي، يُبنى فهرسه الأساسي في الذاكرة لأجزاء البيانات التي سيقرأها الاستعلام، ثم تُستبعد النطاقات غير اللازمة منه بالطريقة نفسها المتبعة مع الإسقاط المُجسَّد. يعرض التقرير عدد العلامات والصفوف التي سيقرأها الإسقاط، وقيمة read_ratio مقارنةً بالقراءة من الجدول الأساسي، وقيمة verdict تبيّن ما إذا كان مُحسِّن الاستعلامات سيختاره. وعند ضبط force_optimize_projection = 1 أو prefer_optimize_projection = 1 يستخدم مُحسِّن الاستعلامات أي إسقاط صالح للاستخدام، ولذا يُعرض الإسقاط الذي لم يكن ليختاره بناءً على التكلفة بالحالة chosen (forced). وعندما تعتمد النتيجة على طريقة تنظيم جزء بيانات الإسقاط، تعرض marks_span نطاق التقديرات، ويُعرض القرار الذي قد يميل إلى أيٍّ من الاتجاهين بالحالة too close to call. وفي الجداول الكبيرة، يقرأ التقدير عيّنة من الحبيبات (راجع projection_scan_budget_rows). تُعرض بالحالة not_applicable الإسقاطات التجميعية، والإسقاطات التي تحتوي على عبارة WHERE، أو فهارس تخطٍّ خاصة بها، أو مفتاح قائم على ترتيب التثبيت أو على عمود افتراضي، أو تخزّن _block_number، وكذلك الإسقاطات التي لا تغطي جميع الأعمدة التي يقرأها الاستعلام. تُتجاهل force_optimize_projection_name وpreferred_optimize_projection_name، ولا يتسبب force_optimize_projection في فشل العبارة. الصيغة
الإعدادات
  • projection_scan_budget_rows — الحد الأقصى لعدد الصفوف التي يمكن أن يقرأها تقدير الإسقاط قبل أن ينتقل إلى عيّنة من الحبيبات. القيمة الافتراضية: 10000000. وتعني القيمة 0 عدم وجود حد، ويمكن للإعداد max_rows_to_read أن يخفّض هذا الحد. تقرأ العيّنة نحو 30 حبيبة على الأقل وحبيبة واحدة لكل جزء بيانات، وإذا لم يسمح max_rows_to_read بذلك يكون التقدير unsupported.
  • empirical — تشغّل القيمة 1 (الافتراضية) الفهرس في الذاكرة على الحبيبات المتبقية بعد الاستبعاد المبدئي لقياس نسبة التخطي (بوصفها حدًا أعلى). أما القيمة 0 فتتجاوز هذه الخطوة. وفي الحالتين، إذا لم يُنتج التقييم التجريبي نتيجة (بسبب تعطيله أو تعذّر تقييم الفهرس في الذاكرة)، يعتمد المُقدِّر على إحصاءات الأعمدة، ثم يكتفي بملخص لقابلية التطبيق إذا لم تتوفر أيٌّ من الطريقتين.
المخرجات
  • source — كيفية احتساب التقدير.
    • empirical: جرى بناء الفهرس في الذاكرة على الحبيبات المتبقية بعد استبعاد خط الأساس، ثم عُدَّت الحبيبات التي سيتجاوزها الفهرس. وهذا يمثل حدًا أعلى — راجع القيود في CREATE HYPOTHETICAL INDEX.
    • statistical: مشتق من إحصاءات الأعمدة. يُستخدم عندما يكون empirical معطّلًا (empirical = 0) أو عندما يتعذر على empirical إنتاج نتيجة، مع توفّر إحصاءات أعمدة للأعمدة ذات الصلة.
    • applicability_only: الفهرس قابل للتطبيق على المسند، لكن لم يُنتج لا التقدير التجريبي ولا الإحصائي نتيجة (مثلًا، empirical = 0 مع عدم تعريف إحصاءات أعمدة). ويعرض skip_ratio: 0.0% بوصفه حدًا محافظًا.
  • empirical_reason — سبب تعذّر تشغيل التقدير التجريبي. لا يظهر إلا مع empirical_status: unsupported. على سبيل المثال، تؤدي قيمة غير صفرية لـmerge_tree_min_rows_for_seek أو merge_tree_min_bytes_for_seek إلى أن تدمج القراءة الفعلية نطاقات العلامات، وهو ما لا يحاكيه العد لكل حبيبة، لذا يعود التقدير إلى statistical أو applicability_only.
  • sampled_parts / sampled_marks — <baseline-pruned> / <total in the table>. يوضح ذلك أي نسبة من الجدول بقيت بعد استبعاد PK والتقسيم والفهارس الموجودة، أي ما يُستخدم كمدخل إلى الفهرس الافتراضي. بالنسبة إلى الإسقاط، تكون قيمة sampled_marks هي <marks read> / <marks in the parts the query reads>، وإذا كان العدد الأول أقل من الثاني، فهذا يعني أن التقدير استند إلى عينة.
  • est_bytes — تقدير لعدد البايتات المقروءة، مشتق من متوسط حجم الصف في الجدول، لذا فهو تقريبي ويتفاوت حسب التخزين والضغط. لا يظهر سطر خط الأساس إلا عندما يقرأ الاستعلام صفوفًا؛ ولا يظهر السطر الخاص بكل مرشح إلا عندما يكون تقدير بايتات خط الأساس معروفًا.
يُكتب الإعداد Inline بين WHATIF وSELECT — ولا توجد الكلمة المفتاحية SETTINGS (وهذا يطابق الطريقة التي تقبل بها متغيرات EXPLAIN الأخرى خياراتها). إذا لم تكن هناك فهارس افتراضية ولا إسقاطات افتراضية معرّفة للجدول، فسيعرض EXPLAIN WHATIF القيمة status: not_applicable مع تلميح لإنشاء واحد منها. الصف المجمّع (مرشحون متعددون) عند تقييم مرشحَين أو أكثر تجريبيًا، يُلحِق EXPLAIN WHATIF كتلة إضافية واحدة باسم (combined: idx_a, idx_b, ...) بعد الصفوف الخاصة بكل مرشح. وتعرض هذه الكتلة الفائدة المشتركة لوجود جميع تلك الفهارس معًا: إذ لا تحتفظ القراءة الفعلية بحبيبة إلا إذا اجتازت كل فهارس التخطي، لذا فإن التقدير المجمّع هو تقاطع الحبيبات المتبقية لدى المرشحين. ومن ثم تكون قيمة skip_ratio الخاصة به مساوية على الأقل لقيمة أفضل مرشح منفرد — فالفهارس المتكاملة تستبعد معًا قدرًا أكبر، بينما لا تُحدث الفهارس المتكررة أي تغيير فيها. لا يُسهم إلا المرشحون ذوو source: empirical، لأن الصف المجمّع يُبنى بتقاطع مجموعات الحبيبات المتبقية لكل منهم. أما المرشحون المقدَّرون بـstatistical أو applicability_only فلا تتوفر لديهم بيانات على مستوى الحبيبة، ولذلك يُستبعدون؛ وعليه لا تظهر الكتلة المجمّعة إلا عندما يُنتج مرشحان على الأقل تقديرًا تجريبيًا، وتُحذف فيما عدا ذلك (على سبيل المثال عند empirical = 0). وتُقرأ حقول التقدير فيها كما في الكتلة التجريبية الخاصة بكل مرشح، باستثناء أن elapsed_us تساوي 0 — إذ يُشتق التقدير المجمّع من عمليات الفحص الخاصة بكل مرشح، لا من فحص جديد. والاسم الاصطناعي (combined: ...) مجرد تسمية في التقرير، ولا يمكن استخدامه مع force_data_skipping_indices. مثال تجريبي
سيستبعد فهرس minmax الافتراضي 99 علامة من أصل 100، لتبقى علامة واحدة — skip_ratio: 99.0%. (est_bytes تقدير يستند إلى متوسط حجم الصف، لذا قد تختلف القيمة الفعلية.) مثال على المسار الإحصائي تكون الإحصاءات الخاصة بالأعمدة غير مفعّلة افتراضيًا. لاستخدام المسار statistical، عرّفها أولًا على الأعمدة ذات الصلة ثم انتظر حتى تكتمل عملية mutation الخاصة بـ materialize:
ثم عطِّل المسار التجريبي ليعتمد المُقدِّر على إحصاءات الأعمدة:
يأتي الرقم من انتقائية إحصاءات العمود للتعبير b < 10 (نحو 10 صفوف من أصل 10000)، ويُورَد باعتباره حدًا أعلى لـ skip_ratio. ولا توجد sampled_parts / sampled_marks — إذ لم تُقرأ أي بيانات. إذا لم يكن أيٌّ من المسارين متاحًا (مثلًا، empirical = 0 ولم تُعرَّف أي إحصاءات أعمدة)، فإن المُقدِّر يُرجع source: applicability_only وskip_ratio: 0.0% بشكل متحفّظ. الإسقاطات الافتراضية يُقدَّر الإسقاط العادي بالطريقة نفسها، بالاستناد إلى أجزاء البيانات التي سيقرأها الاستعلام بعد الاستبعاد باستخدام المفتاح الأساسي واستبعاد التقسيمات:
يشير marks وrows إلى العلامات والصفوف التي ستصل إليها قراءة الإسقاط نفسه، لا إلى حصة من قراءة الجدول الأساسي، لأن حبيبة الإسقاط تضم صفوفًا مختلفة عن حبيبة الجدول الأساسي. وتمثل read_ratio نسبة تلك العلامات إلى علامات الجدول الأساسي؛ لذا تعني 0.02x أن العمل أقل بخمسين مرة، بينما تعني 15x أنه أكثر بخمس عشرة مرة. وتطبّق verdict قاعدة المُحسِّن نفسه — أن يكون عدد العلامات أقل مما في الجدول الأساسي، أو مساويًا له إذا كان الإسقاط يلبّي متطلبات ORDER BY في الاستعلام — لذا قد يكون المرشح applicable ومع ذلك لا يقع عليه الاختيار.

EXPLAIN TABLE OVERRIDE

يعرض ناتج تطبيق تجاوز على مخطط جدول تم الوصول إليه عبر دالة جدول. كما يُجري بعض عمليات التحقق، ويُطلق استثناءً إذا كان هذا التجاوز سيتسبب في أي نوع من الإخفاق. مثال افترض أن لديك جدول MySQL بعيدًا على النحو التالي:
Query
Query
Response
لم تكتمل عملية التحقق، لذا فإن نجاح الاستعلام لا يضمن أن التجاوز لن يسبب مشكلات.