Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

EXPLAIN

يعرض خطة تنفيذ عبارة.

الصيغة:

EXPLAIN [AST | SYNTAX | QUERY TREE | PLAN | PIPELINE | ANALYZE | ESTIMATE | TABLE OVERRIDE | WHATIF] [setting = value, ...]
    [
      SELECT ... |
      tableFunction(...) [COLUMNS (...)] [ORDER BY ...] [PARTITION BY ...] [PRIMARY KEY] [SAMPLE BY ...] [TTL ...]
    ]
    [FORMAT ...]

مثال:

EXPLAIN SELECT sum(number) FROM numbers(10) UNION ALL SELECT sum(number) FROM numbers(10) ORDER BY sum(number) ASC FORMAT TSV;
Output: sum(number)

Union

أنواع EXPLAIN

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

EXPLAIN AST

يعرض AST للاستعلام. يدعم جميع أنواع الاستعلامات، وليس SELECT فقط.

الإعدادات:

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

أمثلة:

EXPLAIN AST SELECT 1;
SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 1)
   ExpressionList (children 1)
    Literal UInt64_1
EXPLAIN AST ALTER TABLE t1 DELETE WHERE date = today();
  explain
  AlterQuery  t1 (children 1)
   ExpressionList (children 1)
    AlterCommand 27 (children 1)
     Function equals (children 1)
      ExpressionList (children 2)
       Identifier date
       Function today (children 1)
        ExpressionList

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.

أمثلة:

Querysql
EXPLAIN SYNTAX SELECT * FROM system.numbers AS a, system.numbers AS b, system.numbers AS c WHERE a.number = b.number AND b.number = c.number;
Responsesql
SELECT *
FROM system.numbers AS a, system.numbers AS b, system.numbers AS c
WHERE (a.number = b.number) AND (b.number = c.number)

باستخدام run_query_tree_passes:

Querysql
EXPLAIN SYNTAX run_query_tree_passes = 1 SELECT * FROM system.numbers AS a, system.numbers AS b, system.numbers AS c WHERE a.number = b.number AND b.number = c.number;
Responsesql
SELECT
    __table1.number AS `a.number`,
    __table2.number AS `b.number`,
    __table3.number AS `c.number`
FROM system.numbers AS __table1
ALL INNER JOIN system.numbers AS __table2 ON __table1.number = __table2.number
ALL INNER JOIN system.numbers AS __table3 ON __table2.number = __table3.number

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

الإعدادات:

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

مثال:

EXPLAIN QUERY TREE SELECT id, value FROM test_table;
QUERY id: 0
  PROJECTION COLUMNS
    id UInt64
    value String
  PROJECTION
    LIST id: 1, nodes: 2
      COLUMN id: 2, column_name: id, result_type: UInt64, source_id: 3
      COLUMN id: 4, column_name: value, result_type: String, source_id: 3
  JOIN TREE
    TABLE id: 3, table_name: default.test_table

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 SELECT sum(number) FROM numbers(10) GROUP BY number % 4  LIMIT 1;
Output: sum(number)

Limit (preliminary LIMIT)

عند ضبط json = 1، تُمثَّل خطة تنفيذ الاستعلام بصيغة JSON. كل عقدة هي قاموس يحتوي دائمًا على المفاتيح Node Type وNode Id وPlans. تكون Node Type سلسلة نصية تتضمن اسم الخطوة، ويكون Node Id معرّفًا فريدًا للخطوة (اسم الخطوة مع لاحقة رقمية، مثل Union_10). أما Plans فهي مصفوفة من أوصاف الخطوات الفرعية. وقد تُضاف مفاتيح اختيارية أخرى بحسب نوع العقدة والإعدادات.

مثال:

EXPLAIN json = 1, description = 0 SELECT 1 UNION ALL SELECT 2 FORMAT TSVRaw;
[
  {
    "Plan": {
      "Node Type": "Union",
      "Node Id": "Union_10",
      "Plans": [
        {
          "Node Type": "Expression",
          "Node Id": "Expression_13",
          "Plans": [
            {
              "Node Type": "ReadFromStorage",
              "Node Id": "ReadFromStorage_0"
            }
          ]
        },
        {
          "Node Type": "Expression",
          "Node Id": "Expression_16",
          "Plans": [
            {
              "Node Type": "ReadFromStorage",
              "Node Id": "ReadFromStorage_4"
            }
          ]
        }
      ]
    }
  }
]

عند ضبط description = 1، يُضاف المفتاح Description إلى الخطوة:

{
  "Node Type": "ReadFromStorage",
  "Description": "SystemOne"
}

عند ضبط header = 1، يُضاف المفتاح Header إلى الخطوة على هيئة مصفوفة من الأعمدة.

مثال:

EXPLAIN json = 1, description = 0, header = 1 SELECT 1, 2 + dummy;
[
  {
    "Plan": {
      "Node Type": "Expression",
      "Node Id": "Expression_5",
      "Header": [
        {
          "Name": "1",
          "Type": "UInt8"
        },
        {
          "Name": "plus(2, dummy)",
          "Type": "UInt16"
        }
      ],
      "Plans": [
        {
          "Node Type": "ReadFromStorage",
          "Node Id": "ReadFromStorage_0",
          "Header": [
            {
              "Name": "dummy",
              "Type": "UInt8"
            }
          ]
        }
      ]
    }
  }
]

مع indexes = 1، يُضاف المفتاح Indexes. ويحتوي على مصفوفة من الفهارس المستخدمة. يُوصَف كل فهرس بتنسيق JSON مع المفتاح Type (سلسلة نصية بقيمة Partition Min-Max أو Partition أو Statistics أو PrimaryKey أو Skip) والمفاتيح الاختيارية التالية:

  • Name — اسم الفهرس (يُستخدم حاليًا فقط مع فهارس Skip).
  • Keys — مصفوفة الأعمدة التي يستخدمها الفهرس.
  • Condition — الشرط المستخدم.
  • Description — وصف الفهرس (يُستخدم حاليًا فقط مع فهارس Skip).
  • Parts — عدد الأجزاء بعد/قبل تطبيق الفهرس.
  • Granules — عدد الحبيبات بعد/قبل تطبيق الفهرس.
  • Ranges — عدد نطاقات الحبيبات بعد تطبيق الفهرس.

مثال:

"Node Type": "ReadFromMergeTree",
"Indexes": [
  {
    "Type": "Partition Min-Max",
    "Keys": ["y"],
    "Condition": "(y in [1, +inf))",
    "Parts": 4/5,
    "Granules": 11/12
  },
  {
    "Type": "Partition",
    "Keys": ["y", "bitAnd(z, 3)"],
    "Condition": "and((bitAnd(z, 3) not in [1, 1]), and((y in [1, +inf)), (bitAnd(z, 3) not in [1, 1])))",
    "Parts": 3/4,
    "Granules": 10/11
  },
  {
    "Type": "PrimaryKey",
    "Keys": ["x", "y"],
    "Condition": "and((x in [11, +inf)), (y in [1, +inf)))",
    "Parts": 2/3,
    "Granules": 6/10,
    "Search Algorithm": "generic exclusion search"
  },
  {
    "Type": "Skip",
    "Name": "t_minmax",
    "Description": "minmax GRANULARITY 2",
    "Parts": 1/2,
    "Granules": 2/6
  },
  {
    "Type": "Skip",
    "Name": "t_set",
    "Description": "set GRANULARITY 2",
    "": 1/1,
    "Granules": 1/2
  }
]

عند تعيين projections = 1، يُضاف مفتاح Projections، ويحتوي على مصفوفة من الإسقاطات المُحللة. يُوصف كل إسقاط بصيغة JSON وفق المفاتيح التالية:

  • Name — اسم الإسقاط.
  • Condition — شرط المفتاح الأساسي المستخدم في الإسقاط.
  • Description — وصف كيفية استخدام الإسقاط (على سبيل المثال، التصفية على مستوى الجزء).
  • Selected Parts — عدد الأجزاء التي اختارها الإسقاط.
  • Selected Marks — عدد العلامات المختارة.
  • Selected Ranges — عدد النطاقات المختارة.
  • Selected Rows — عدد الصفوف المختارة.
  • Filtered Parts — عدد الأجزاء التي جرى تخطيها بسبب التصفية على مستوى الجزء.

مثال:

"Node Type": "ReadFromMergeTree",
"Projections": [
  {
    "Name": "region_proj",
    "Description": "Projection has been analyzed and is used for part-level filtering",
    "Condition": "(region in ['us_west', 'us_west'])",
    "Search Algorithm": "binary search",
    "Selected Parts": 3,
    "Selected Marks": 3,
    "Selected Ranges": 3,
    "Selected Rows": 3,
    "Filtered Parts": 2
  },
  {
    "Name": "user_id_proj",
    "Description": "Projection has been analyzed and is used for part-level filtering",
    "Condition": "(user_id in [107, 107])",
    "Search Algorithm": "binary search",
    "Selected Parts": 1,
    "Selected Marks": 1,
    "Selected Ranges": 1,
    "Selected Rows": 1,
    "Filtered Parts": 2
  }
]

مع actions = 1، تعتمد المفاتيح المضافة على نوع الخطوة.

مثال:

EXPLAIN json = 1, actions = 1, description = 0 SELECT 1 FORMAT TSVRaw;
[
  {
    "Plan": {
      "Node Type": "Expression",
      "Node Id": "Expression_5",
      "Expression": {
        "Inputs": [
          {
            "Name": "dummy",
            "Type": "UInt8"
          }
        ],
        "Actions": [
          {
            "Node Type": "INPUT",
            "Result Type": "UInt8",
            "Result Name": "dummy",
            "Arguments": [0],
            "Removed Arguments": [0],
            "Result": 0
          },
          {
            "Node Type": "COLUMN",
            "Result Type": "UInt8",
            "Result Name": "1",
            "Column": "Const(UInt8)",
            "Arguments": [],
            "Removed Arguments": [],
            "Result": 1
          }
        ],
        "Outputs": [
          {
            "Name": "1",
            "Type": "UInt8"
          }
        ],
        "Positions": [1]
      },
      "Plans": [
        {
          "Node Type": "ReadFromStorage",
          "Node Id": "ReadFromStorage_0"
        }
      ]
    }
  }
]

مع compact = 0 وactions = 1، يمكن رؤية خطوات Expression مع معلومات مفصلة عن التعبيرات:

EXPLAIN actions = 1, compact = 0 SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
Output: sum(number)

Expression ((Project names + Projection))
│  Actions: INPUT : 0 -> sum(__table1.number) UInt64 : 0
│           INPUT :: 1 -> modulo(__table1.number, 4_UInt8) UInt8 : 1
│           ALIAS sum(__table1.number) :: 0 -> sum(number) UInt64 : 2
│  Positions: 2
└──Aggregating
   │  Keys: number MOD 4
   │  Aggregates: sum(number)
   │  Skip merging: 0
   └──Expression ((Before GROUP BY + Change column names to column identifiers))
      │  Actions: INPUT : 0 -> number UInt64 : 0
      │           COLUMN Const(UInt8) -> 4_UInt8 UInt8 : 1
      │           ALIAS number :: 0 -> __table1.number UInt64 : 2
      │           FUNCTION modulo(__table1.number : 2, 4_UInt8 :: 1) -> modulo(__table1.number, 4_UInt8) UInt8 : 0
      │  Positions: 0 2
      └──ReadFromSystemNumbers
            Output: number

عند تعيين distributed = 1، يتضمن الناتج ليس فقط خطة الاستعلام المحلية، بل أيضاً خطط الاستعلام التي ستُنفَّذ على العقد البعيدة. يُفيد هذا في تحليل الاستعلامات الموزعة وتصحيح أخطائها.

مثال مع جدول موزّع:

EXPLAIN distributed=1 SELECT * FROM remote('127.0.0.{1,2}', numbers(2)) WHERE number = 1;
Union
  Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection)))))
    Filter ((WHERE + Change column names to column identifiers))
      ReadFromSystemNumbers
  Expression ((Project names + (Projection + Change column names to column identifiers)))
    ReadFromRemote (Read from remote replica)
      Expression ((Project names + Projection))
        Filter ((WHERE + Change column names to column identifiers))
          ReadFromSystemNumbers

مثال مع النسخ المتماثلة المتوازية:

SET enable_parallel_replicas = 2, max_parallel_replicas = 2, cluster_for_parallel_replicas = 'default';

EXPLAIN distributed=1 SELECT sum(number) FROM test_table GROUP BY number % 4;
Expression ((Project names + Projection))
  MergingAggregated
    Union
      Aggregating
        Expression ((Before GROUP BY + Change column names to column identifiers))
          ReadFromMergeTree (default.test_table)
      ReadFromRemoteParallelReplicas
        BlocksMarshalling
          Aggregating
            Expression ((Before GROUP BY + Change column names to column identifiers))
              ReadFromMergeTree (default.test_table)

في كلا المثالَين، تُظهر خطةُ الاستعلام تدفق التنفيذ الكامل بما في ذلك الخطوات المحلية والبعيدة.

عند تعيين pretty = 1، تُعرض شجرة الخطة باستخدام أحرف رسم الخطوط بدلاً من المسافات البادئة، وتظهر معلومات إضافية للخطوات الرئيسية:

  • تُطبَع أعمدة ناتج الاستعلام في أعلى الخطة.
  • تُعرَض التعبيرات في عوامل التصفية، ومفاتيح التجميع، وأوصاف الفرز، ودوال النوافذ بصياغة مقروءة للبشر شبيهة بـ SQL (مثلًا، a + 1 > 5 بدلًا من greater(plus(a, 1), 5)). وتُزال بادئات معرّفات الأعمدة الداخلية (مثل __table1.) لتوضيح العرض.
  • تعرض خطوات المصدر (مثل ReadFromMergeTree) أعمدة ناتجها.
  • تعرض خطوات التصفية شرط التصفية بصياغة SQL. وعند وجود عوامل تصفية للربط وقت التشغيل، تُعرض بشكل منفصل.
  • تعرض خطوات التجميع المفاتيح والدوال التجميعية مع وسيطاتها (مثل sum(c) وcount()).
  • تُظهر مجموعات IN من قيم tuple الحرفية قيمها (مقتطعةً للمجموعات الكبيرة)، وتُوسَم المجموعات المستندة إلى استعلامات فرعية على أنها subquery1 وsubquery2 وما إلى ذلك، وتُظهر المجموعات من جداول محرك Set اسم الجدول.
  • تعرض خطوات الربط علاقة الربط باستخدام ترميز رياضي، والعدد التقديري لصفوف النتيجة، وأي أعمدة ناتج تأتي من الجانب الأيسر مقابل الجانب الأيمن. وتُستخدم الرموز التالية لتمثيل أنواع الربط المختلفة:
Symbol Join Type
Inner Join
Left Join
Right Join
Full Join
Left Semi Join
Right Semi Join
with strikethrough Left Anti Join
with strikethrough Right Anti Join
× Cross Join

على سبيل المثال، t1 ⟕ t2 تعني LEFT JOIN بين الجدولين t1 وt2. ويشير الرقم بين القوسين بعد اسم الجدول (مثل t1[100]) إلى العدد التقديري للصفوف عندما تكون إحصاءات الجدول متاحة.

يعمل الخيار pretty جيدًا مع compact = 1، إذ يُخفي خطوات Expression ومعلومات الإجراءات التفصيلية، مما يجعل الخطة أسهل في القراءة.

مثال تفصيلي يتضمن عمليات JOIN:

CREATE TABLE t1 (id UInt64, value String) ENGINE = MergeTree ORDER BY id;
CREATE TABLE t2 (id UInt64, value String) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 SELECT number, toString(number) FROM numbers(100);
INSERT INTO t2 SELECT number, toString(number) FROM numbers(100);

EXPLAIN actions = 1, compact = 1, pretty = 1
SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id FORMAT Raw;
Output: id, value, id, value

Join (JOIN FillRightFirst)
│  t1[100] ⋈ t2[100]
│  Type: inner | Strictness: all | Algorithm: SpillingHashJoin(HashJoin)
│  Result rows: 100
│  Join conditions: id = id
│  Output:
│    Left:  id, value
│    Right: id, value
├──ReadFromMergeTree (default.t1)
│     Read type: Default
│     Parts: 1 | Granules: 1
│     Output: id, value
│     Runtime filters: RF1(id, id from default.t2)
└──BuildRuntimeFilter (Build runtime join filter on id)
   │  Filter id: RF1
   │  Source table: default.t2
   └──ReadFromMergeTree (default.t2)
         Read type: Default
         Parts: 1 | Granules: 1
         Output: id, value

EXPLAIN PIPELINE

الإعدادات:

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

عندما تكون compact=0 وgraph=1، ستتضمن أسماء المعالجات لاحقة إضافية تحتوي على معرّف فريد للمعالج.

مثال:

EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 4;
(Union)
(Expression)
ExpressionTransform
  (Expression)
  ExpressionTransform
    (Aggregating)
    Resize 2

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.

مثال:

EXPLAIN ANALYZE SELECT number % 10 AS k, count() FROM numbers_mt(1000000) GROUP BY k;
Query summary:
  Time:        10.72 ms (planning 6.45 ms · execution 4.26 ms)
  Read:        1.00 million rows, 8.00 MB (234.49 million rows/s., 1.88 GB/s.)
  Peak memory: 28.98 KiB

Output: number MOD 10, count()

Expression ((Project names + Projection))
│  I/O: rows 10 → 10 · 90 B → 90 B
│    time 21.82 us (0.5%) · parallelism 0.98/1
└──Aggregating
   │  Keys: number MOD 10
   │  Aggregates: count()
   │  Skip merging: 0
   │  I/O: rows 1.00 million → 10 (0.00%) · 1.00 MB → 90 B
   │    Stage (partial aggregation): time 868.45 us (20.4%) · parallelism 3.80/15
   │    Stage (final aggregation): time 445.27 us (10.4%) · parallelism 1.11/16
   └──Expression ((Before GROUP BY + Change column names to column identifiers))
      │  I/O: rows 1.00 million → 1.00 million · 8.00 MB → 1.00 MB
      │    time 677.07 us (15.9%) · parallelism 4.31/15
      └──ReadFromSystemNumbers
            Output: number
            I/O: rows 0 → 1.00 million · 0 B → 8.00 MB
              time 993.94 us (23.3%) · parallelism 7.52/15

لنفحص الناتج. أولاً، لننظر إلى الترويسة.

   Query summary:
     Time:        <total> (planning <planning> · execution <execution>)
     Read:        <rows> rows, <bytes> (<rows/s>, <bytes/s>)
     Peak memory: <peak>
  • Time — الوقت الإجمالي، مقسَّماً إلى مرحلتَي التخطيط (أي إنشاء الخطة + تحسين الخطة + بناء pipeline) والتنفيذ (تشغيل pipeline).
  • Read — الصفوف والبايتات غير المضغوطة المقروءة من الجداول، مع معدل النقل — وهي الأرقام نفسها التي يَعرضها تذييل الاستعلام العادي تحت اسم "Processed".
  • Peak memory — ذروة الذاكرة التي استخدمها الاستعلام.

والآن لنلقِ نظرة على الأسطر الجديدة التي تظهر في خطة الاستعلام.

I/O: rows <in> → <out> (<selectivity>%) · <bytes_in> → <bytes_out>
  [Stage (<stage>): ]time <t> (<share>%) · parallelism <avg>/<max>

يُبلَّغ عن الصفوف والبايتات مرة واحدة للخطوة بأكملها (سطر 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).

خطوات الربط

في خطوة ربط، يطبع EXPLAIN ANALYZE أسطر المشاركة لكل جانب — Left وRight — ثم أي أسطر خاصة بتنفيذ الربط. يقابل Left وRight الجانبين المنطقيين في SQL. في معظم الحالات، يكون Left أيضًا جانب الفحص في الربط، بينما يكون Right جانب البناء. لكن ليس هذا هو الحال دائمًا بسبب التبديل الذي قد يحدث أثناء تنفيذ الربط. تُغطّى جميع قيم join_algorithm (hash، parallel_hash، grace_hash، partial_merge، full_sorting_merge، parallel_full_sorting_merge، direct)، وكذلك التنفيذان اللذان لا يمكن للإعداد اختيارهما: ربط CROSS أو COMMA، وأي قسم ON لا يحتوي على مساواة مفتاح، ومحرك الجدول Join. يُبلّغ معظمها عن كلا الجانبين، بينما يُبلّغ بعضها فقط عن الجانب الذي يجسّده ماديًا (على سبيل المثال، يطبع directLeft: فقط).

تشترك الأسطر الخاصة بكل جانب في البنية نفسها:

Left:  rows <left_rows>  · matched <matched_left_rows>  · match rate <match_rate>% · fanout <fanout>
Right: rows <right_rows> · matched <matched_right_rows> · match rate <match_rate>% · fanout <fanout>

لكل جانب، يعرض EXPLAIN ANALYZE ما يلي:

  • 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.

Fanout

يقيس fanout مدى تضاعف الصفوف:

matched output rows = <output_rows> - <NULL-padded rows of both sides>
fanout              = <matched output rows> / <matched_rows of that side>

يُنتج الربط الخارجي صف إخراج واحدًا مُبطّنًا بـ 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 كل تركيبة قابلة للجمع. فالجانب الذي يستطيع الربط الإبلاغ عنه يتحدد بما يتعين على ذلك الربط تنفيذه على أي حال، لذا يعتمد على الخوارزمية، وكذلك على النوع والصرامة.

عائلة التجزئة. تتفق hash وparallel_hash وgrace_hash دائمًا فيما بينها:

الربط matched الأيسر matched الأيمن
ALL INNER, ALL LEFT, ALL RIGHT, ALL FULL نعم نعم
SEMI LEFT, ANTI LEFT نعم لا
ANY RIGHT, ANTI RIGHT لا نعم
ASOF (داخلي) نعم لا
SEMI RIGHT لا لا
ANY INNER, ANY LEFT, ASOF LEFT لا لا

يكون الجانب الأيمن غير متاح كلما احتفظ الربط بصف واحد فقط لكل مفتاح في جدول التجزئة، كما تفعل عمليات الربط 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 ثابت. لا يُجمع أي من الجانبين، كما هو موضح أعلاه.

عندما تُرجع خوارزميتان رقمًا، تكون الأرقام متطابقة. تمتلك خوارزميات الدمج معلومات إضافية فحسب، لكنها لا تختلف في تعريف ما يُعدّ تطابقًا.

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

لنلقِ نظرة على الأسطر الإضافية التي يضيفها كل تنفيذ لعملية الربط.

بالنسبة إلى عمليتَي الربط hash وparallel_hash، ولمحرك الجدول Join، يصف السطر Hash table: جدول التجزئة المُنشأ من الجدول الأيمن:

Hash table: unique keys <unique_keys> · memory <peak_memory>
  • unique keys <unique_keys> — عدد المفاتيح الفريدة المخزّنة في جدول التجزئة أثناء مرحلة البناء.
  • memory <peak_memory> — ذروة الذاكرة التي استخدمها جدول التجزئة أثناء مرحلة البناء.

بالنسبة إلى ربط grace_hash، يعرض السطر Hash table: أيضًا كيفية تكيّف الربط مع حد الذاكرة، بينما يبيّن السطر Spill: ما إذا كانت البيانات قد نُقلت إلى القرص:

Hash table: unique keys <unique_keys> · memory <peak_memory> · buckets <buckets> · rehashes <rehashes>
Spill: yes · left spilled <left_spilled_bytes> · right spilled <right_spilled_bytes>
  • 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):

Right: rows <right_rows> · matched <matched_right_rows> · size <right_size> · blocks <right_blocks> · storage <in-memory|external> · match rate <match_rate>% · fanout <fanout>
  Stage (build): time <t> (<share>%) · parallelism <avg>/<max> · sort time <build_sort_time> · sort share <build_sort_share>%
  Stage (probe): time <t> (<share>%) · parallelism <avg>/<max> · sort time <probe_sort_time> · sort share <probe_sort_share>%
  • 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، ولأي قسم ON دون مساواة مفتاح، يصف السطر Buffer: كيفية الاحتفاظ بالجدول الأيمن في الذاكرة، ويعرض السطر Spill: ما إذا كان قد رُحِّل إلى القرص:

Buffer: memory <peak_memory> · compressed <yes|no>
Spill: yes · right spilled <right_spilled_bytes>
  • memory <peak_memory> — ذروة الذاكرة التي استهلكها الجدول الأيمن المُخزَّن مؤقتًا.
  • compressed <yes|no> — ما إذا كانت كتلة واحدة مخزَّنة مؤقتًا على الأقل قد ضُغطت؛ وفي هذه الحالة تفكّ القارئات ضغط كل كتلة مخزَّنة.
  • Spill: — علامة yes/no نفسها المستخدمة مع grace_hash، ويعرض right spilled <right_spilled_bytes> عدد البايتات المضغوطة المكتوبة على القرص.

يعرض كلا الجانبين هنا matched not collected: إذ إن المسند الثابت إما يقرن كل صف من الجدول الأيسر بكل صف من الجدول الأيمن، أو لا يقرن أي صف منها، لذا لا يمكن تحديد الصفوف الفردية التي تطابقت.

في عملية ربط مع محرك الجدول Join، يُبلّغ عن كلا الجانبين، إلى جانب السطر Hash table: الذي يصف الجدول المُنشأ مسبقًا. ويعرض الجانب الأيمن عدد الصفوف المخزنة في المحرك، لا صفوف عملية بناء خاصة بكل query.

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

عند استخدام processors = 1، يُطبَع سطر إضافي أسفل كل مرحلة يوضح توزيع الزمن المنقضي عبر معالجات المرحلة:

Time per processor (<n>): min <t> · median <t> · max <t> · sum <t>

يشير <n> إلى عدد المعالِجات في هذه المرحلة. ويشير الفارق الكبير بين median وmax إلى اختلال في توازن الحمل بين المعالِجات المتوازية.

EXPLAIN ESTIMATE

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

مثال

إنشاء جدول:

Querysql
CREATE TABLE ttt (i Int64) ENGINE = MergeTree() ORDER BY i SETTINGS index_granularity = 16, write_final_mark = 0;
INSERT INTO ttt SELECT number FROM numbers(128);
OPTIMIZE TABLE ttt;
Querysql
EXPLAIN ESTIMATE SELECT * FROM ttt;
Responsetext
┌─database─┬─table─┬─parts─┬─rows─┬─marks─┐
│ default  │ ttt   │     1 │  128 │     8 │
└──────────┴───────┴───────┴──────┴───────┘

EXPLAIN WHATIF

يقدّر الفائدة التي يمكن أن يوفّرها فهرس تخطٍّ افتراضي لاستعلام SELECT، من دون إجراء materializing للفهرس على القرص. حدِّد مرشحًا واحدًا أو أكثر باستخدام CREATE HYPOTHETICAL INDEX، ثم شغّل EXPLAIN WHATIF SELECT ... لمعرفة ما يلي لكل مرشح: مدى قابليته للتطبيق، والعدد التقديري للعلامات المقروءة، والبايتات التقديرية، ونسبة التخطي.

الصياغة

EXPLAIN WHATIF [empirical = 0] SELECT ...

الإعدادات

  • empirical — تؤدي القيمة 1 (الافتراضية) إلى تشغيل الفهرس في الذاكرة على الحبيبات التي جرى تقليصها وفق خط الأساس، وذلك لقياس نسبة التخطي (وهي حدٌّ أعلى). أما 0 فتتخطى هذا المسار. وفي كلتا الحالتين، إذا لم يُنتج empirical نتيجة (إما لأنه معطَّل أو لأن الفهرس لا يمكن تقييمه في الذاكرة)، فإن المُقدِّر يعود إلى إحصاءات الأعمدة، ثم أخيرًا إلى ملخص يقتصر على قابلية التطبيق فقط إذا لم يتوفر أيٌّ منهما.

المخرجات

Baseline (after PK + partition + existing indexes):
  table:       db.t
  parts:       1
  marks:       100
  est_bytes:   1.50 MiB             (only when the query reads rows)

With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    15.00 KiB           (only when baseline bytes are known)
  skip_ratio:   99.0%

Estimation:
  source:           empirical | statistical | applicability_only
  empirical_status: ok | unsupported | disabled
  empirical_reason: <reason>        (only when empirical_status = unsupported)
  sampled_parts:    50 / 100        (only when source = empirical)
  sampled_marks:    50 / 100        (only when source = empirical)
  elapsed_us:       631             (only when source = empirical)
  • 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 والتقسيم والفهارس الموجودة، أي ما يُستخدم كمدخل إلى الفهرس الافتراضي.
  • 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.

مثال تجريبي

CREATE TABLE t (a UInt64, b UInt64) ENGINE = MergeTree ORDER BY a
SETTINGS index_granularity = 100;

INSERT INTO t SELECT number, number FROM numbers(10000);

CREATE HYPOTHETICAL INDEX idx_b ON t (b) TYPE minmax GRANULARITY 1;

EXPLAIN WHATIF SELECT * FROM t WHERE b = 42;
Baseline (after PK + partition + existing indexes):
  table:       default.t
  parts:       1
  marks:       100
  est_bytes:   85.52 KiB

With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    875.00 B
  skip_ratio:   99.0%

Estimation:
  source:           empirical
  empirical_status: ok
  sampled_parts:    1 / 1
  sampled_marks:    100 / 100

سيؤدي minmax الافتراضي إلى تقليص عدد العلامات من 100 إلى علامة واحدة — skip_ratio: 99.0%. (القيمة est_bytes هي تقدير مستند إلى متوسط حجم الصف، لذا قد يختلف الرقم الفعلي.)

مثال إحصائي

تكون الإحصاءات الخاصة بالأعمدة غير مفعّلة افتراضيًا. لاستخدام المسار statistical، عرّفها أولًا على الأعمدة ذات الصلة ثم انتظر حتى تكتمل عملية mutation الخاصة بـ materialize:

ALTER TABLE t ADD STATISTICS b TYPE tdigest;
ALTER TABLE t MATERIALIZE STATISTICS b SETTINGS mutations_sync = 1;

ثم عطِّل المسار التجريبي ليعتمد المُقدِّر على إحصاءات الأعمدة:

EXPLAIN WHATIF empirical = 0 SELECT * FROM t WHERE b < 10;
With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    1.66 KiB
  skip_ratio:   99.9%

Estimation:
  source:           statistical
  empirical_status: disabled

يأتي الرقم من انتقائية إحصاءات العمود للتعبير b < 10 (نحو 10 صفوف من أصل 10000)، ويُورَد باعتباره حدًا أعلى لـ skip_ratio. ولا توجد sampled_parts / sampled_marks — إذ لم تُقرأ أي بيانات.

إذا لم يكن أيٌّ من المسارين متاحًا (مثلًا، empirical = 0 ولم تُعرَّف أي إحصاءات أعمدة)، فإن المُقدِّر يُرجع source: applicability_only وskip_ratio: 0.0% بشكل متحفّظ.

EXPLAIN TABLE OVERRIDE

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

مثال

افترض أن لديك جدول MySQL بعيدًا على النحو التالي:

Querysql
CREATE TABLE db.tbl (
    id INT PRIMARY KEY,
    created DATETIME DEFAULT now()
)
Querysql
EXPLAIN TABLE OVERRIDE mysql('127.0.0.1:3306', 'db', 'tbl', 'root', 'clickhouse')
PARTITION BY toYYYYMM(assumeNotNull(created))
Responsetext
┌─explain─────────────────────────────────────────────────┐
│ PARTITION BY uses columns: `created` Nullable(DateTime) │
└─────────────────────────────────────────────────────────┘
Navigation