الانتقال إلى المحتوى الرئيسي

هل تبحث عن دليل؟

اطّلع على دليلنا لأفضل ممارسات JSON للاطلاع على أمثلة وميزات متقدمة واعتبارات تتعلق باستخدام نوع JSON.
يخزّن النوع JSON مستندات JavaScript Object Notation ‏(JSON) في عمود واحد.
في ClickHouse مفتوح المصدر، تم تصنيف نوع بيانات JSON على أنه جاهز للإنتاج في الإصدار 25.3. ولا يُنصح باستخدام هذا النوع في بيئة الإنتاج في الإصدارات السابقة.
لإعلان عمود من النوع JSON، يمكنك استخدام البنية التالية:
حيث تُعرَّف المعلمات في البنية أعلاه كما يلي:

متى تستخدم النوع JSON

صُمِّم النوع JSON للاستعلام عن حقول محددة داخل كائنات JSON وتصفيتها وتجميعها عندما تكون بنيتها ديناميكية أو غير متوقعة. ويحقق ذلك عبر تقسيم كائنات JSON إلى أعمدة فرعية منفصلة، ما يقلل بدرجة كبيرة من حجم البيانات المقروءة ويُسرّع الاستعلامات على الحقول المحددة مقارنةً ببدائل مثل Map أو تحليل السلاسل النصية. لكن هذا ينطوي على بعض المقايضات المهمة:
  • بطء عمليات INSERT - إن تقسيم JSON إلى أعمدة فرعية، وإجراء استنتاج النوع، وإدارة هياكل تخزين مرنة، يجعل عمليات الإدراج أبطأ مقارنةً بتخزين JSON في عمود String بسيط.
  • بطء عند قراءة الكائنات كاملةً - إذا كنت بحاجة إلى استرجاع مستندات JSON كاملة (بدلاً من حقول محددة)، فإن النوع JSON يكون أبطأ من القراءة من عمود String. فالعبء الإضافي الناتج عن إعادة تكوين الكائنات من الأعمدة الفرعية المنفصلة لا يحقق أي فائدة عندما لا تُجري استعلامات على مستوى الحقول.
  • عبء تخزيني إضافي - إن الاحتفاظ بأعمدة فرعية منفصلة يضيف عبئًا بنيويًا مقارنةً بتخزين JSON كسلسلة نصية واحدة.

استخدم النوع JSON عندما:

  • تكون بياناتك ذات بنية ديناميكية أو غير متوقعة، مع مفاتيح تختلف من مستند إلى آخر
  • تتغير أنواع الحقول أو البُنى بمرور الوقت أو تختلف بين السجلات
  • تحتاج إلى الاستعلام أو التصفية أو التجميع على مسارات محددة داخل كائنات JSON التي لا يمكنك توقّع بنيتها مسبقًا
  • تتضمن حالة الاستخدام لديك بيانات شبه مهيكلة مثل السجلات أو الأحداث أو المحتوى الذي ينشئه المستخدم، مع بُنى غير متسقة

استخدم عمود String (أو الأنواع المهيكلة) عندما:

  • تكون بنية بياناتك معروفة ومتسقة — في هذه الحالة، استخدم الأعمدة العادية أو الأنواع Tuple وArray وDynamic وVariant بدلًا من ذلك
  • تُعامَل مستندات JSON على أنها blobs معتمة لا تُخزَّن ولا تُسترجَع إلا كاملةً، من دون تحليل على مستوى الحقول
  • لا تحتاج إلى إجراء استعلامات أو تصفية على حقول JSON الفردية داخل قاعدة البيانات
  • يكون JSON مجرد تنسيق للنقل/التخزين، ولا يُحلَّل داخل ClickHouse
إذا كان JSON مستندًا معتمًا لا يُحلَّل داخل قاعدة البيانات، ويُخزَّن ويُسترجَع فقط، فينبغي تخزينه كحقل String. ولا تظهر مزايا النوع JSON إلا عندما تحتاج إلى الاستعلام أو التصفية أو التجميع بكفاءة على حقول محددة داخل بُنى JSON الديناميكية.يمكنك أيضًا المزج بين النهجين — استخدم الأعمدة القياسية للحقول العلوية المتوقعة، وعمود JSON للأجزاء الديناميكية من الحمولة.

إنشاء JSON

في هذا القسم، سنستعرض الطرق المختلفة التي يمكنك من خلالها إنشاء JSON.

استخدام JSON ضمن تعريف عمود في جدول

Query (Example 1)
Response (Example 1)
Query (Example 2)
Response (Example 2)

استخدام CAST مع ::JSON

يمكن تحويل أنواع مختلفة باستخدام البنية الخاصة ::JSON.

التحويل باستخدام CAST من String إلى JSON

Query
Response

CAST من Tuple إلى JSON

Query
Response

CAST من Map إلى JSON

Query
Response
يتم تخزين مسارات JSON بشكل مسطّح. وهذا يعني أنه عند تنسيق كائن JSON انطلاقًا من مسار مثل a.b.c لا يمكن معرفة ما إذا كان ينبغي إنشاء الكائن بالشكل { "a.b.c" : ... } أو { "a": { "b": { "c": ... } } }. ويفترض تنفيذنا دائمًا الاحتمال الثاني.على سبيل المثال:
استعلام
سيُرجع:
الاستجابة
وليس:

قراءة مسارات JSON كأعمدة فرعية

يدعم النوع JSON قراءة كل مسار بوصفه عمودًا فرعيًا منفصلًا. إذا لم يُحدَّد نوع المسار المطلوب في تعريف النوع JSON، فسيكون العمود الفرعي لهذا المسار دائمًا من النوع Dynamic. على سبيل المثال:
Query
Response
Query (Reading JSON paths as sub-columns)
Response (Reading JSON paths as sub-columns)
يمكنك أيضًا استخدام الدالة getSubcolumn لقراءة الأعمدة الفرعية من النوع JSON:
Query
Response
إذا لم يُعثر على المسار المطلوب في البيانات، فسيُملأ بقيم NULL:
Query
Response
لنتحقق من أنواع بيانات الأعمدة الفرعية المُعادة:
Query
Response
كما نرى، بالنسبة إلى a.b، يكون النوع UInt32 كما حددناه في تعريف نوع JSON، أما في جميع الأعمدة الفرعية الأخرى، فالنوع هو Dynamic. ومن الممكن أيضًا قراءة الأعمدة الفرعية من النوع Dynamic باستخدام الصياغة الخاصة json.some.path.:TypeName:
Query
Response
يمكن تحويل الأعمدة الفرعية في Dynamic إلى أي نوع بيانات. في هذه الحالة، سيتم طرح استثناء إذا تعذّر تحويل النوع الداخلي داخل Dynamic إلى النوع المطلوب:
Query
Response
Query
Response
لقراءة الأعمدة الفرعية بكفاءة من أجزاء Compact في MergeTree، تأكّد من تفعيل إعداد MergeTree ‏write_marks_for_substreams_in_compact_parts.

قراءة الكائنات الفرعية في JSON كأعمدة فرعية

يدعم النوع JSON قراءة الكائنات المتداخلة كأعمدة فرعية من النوع JSON باستخدام الصياغة الخاصة json.^some.path:
Query
Response
Query
Response
عندما تُخزَّن المسارات في البيانات المشتركة الأساسية (map)، فقد لا تكون قراءة الأعمدة الفرعية للكائنات الفرعية فعّالة، لأنها تتطلب فحص بنية البيانات المشتركة بالكامل. أما مع map_with_buckets أو تسلسل البيانات المشتركة advanced، فتكون قراءة الأعمدة الفرعية من البيانات المشتركة محسّنة بدرجة كبيرة.

قراءة الأعمدة الفرعية المجمّعة في JSON

يدعم النوع JSON قراءة مسار باعتباره عمودًا فرعيًا مجمّعًا باستخدام الصياغة الخاصة json.@some.path. ويُرجع العمود الفرعي المجمّع لمسار معيّن ما يلي:
  • القيمة الحرفية المخزّنة في ذلك المسار على أنها Dynamic، إذا كان المسار يحتوي على قيمة حرفية.
  • كائن JSON فرعيًا في ذلك المسار على أنه Dynamic، إذا لم يكن المسار يحتوي على قيمة حرفية ولكن كانت له مسارات فرعية متداخلة.
  • NULL، إذا لم تكن هناك قيمة حرفية أو أي مسارات فرعية لذلك المسار.
ويكون هذا مفيدًا عندما قد يحتوي المسار على قيمة قياسية أو كائن متداخل عبر صفوف مختلفة، كما أنه أسهل من الاستعلام بشكل منفصل عن العمود الفرعي للقيمة الحرفية (json.a) والعمود الفرعي للكائن الفرعي (json.^a). يقارن المثال التالي بين أنواع الأعمدة الفرعية الثلاثة للمسار a:
Query
Response
Query
Response
  • الصف 1: تحتوي a على قيمة حرفية 42. يُرجع json.a هذه القيمة بصيغة Dynamic(Int64)، ويُرجع json.^a كائنًا فرعيًا فارغًا {} (لا توجد مفاتيح متداخلة تحت a)، ويُرجع json.@a القيمة الحرفية 42.
  • الصف 2: تحتوي a على كائن متداخل. يُرجع json.a القيمة NULL (لا توجد قيمة حرفية في هذا المسار)، ويُرجع json.^a الكائن الفرعي بصيغة JSON، ويُرجع json.@a أيضًا الكائن الفرعي بصيغة Dynamic(JSON).
  • الصف 3: a غير موجودة بالكامل. يُرجع كلٌّ من json.a وjson.@a القيمة NULL، بينما يُرجع json.^a كائنًا فارغًا {}.
عند تخزين المسارات في البيانات المشتركة الأساسية (map)، قد تكون قراءة الأعمدة الفرعية المجمّعة غير فعّالة لأنها تتطلب فحص بنية البيانات المشتركة بالكامل. ومع map_with_buckets أو تسلسل البيانات المشتركة advanced، تصبح قراءة الأعمدة الفرعية من البيانات المشتركة محسّنة بدرجة كبيرة.

استنتاج الأنواع للمسارات

أثناء تحليل JSON، يحاول ClickHouse تحديد نوع البيانات الأنسب لكل مسار JSON. ويعمل ذلك بطريقة مشابهة لـ الاستدلال التلقائي على المخطط من بيانات الإدخال، ويُتحكَّم فيه بالإعدادات نفسها: لنلقِ نظرة على بعض الأمثلة:
Query
Response
Query
Response
Query
Response
Query
Response

التعامل مع مصفوفات من كائنات JSON

تُحلَّل مسارات JSON التي تحتوي على مصفوفة من الكائنات على أنها من النوع Array(JSON)، وتُدرَج في عمود Dynamic لذلك المسار. ولقراءة مصفوفة من الكائنات، يمكنك استخراجها من عمود Dynamic كعمود فرعي:
Query
Response
Query
Response
كما قد تكون لاحظت، جرى تقليل المعلمتين max_dynamic_types/max_dynamic_paths في نوع JSON المتداخل مقارنةً بالقيم الافتراضية. وهذا ضروري لتجنّب ازدياد عدد الأعمدة الفرعية بشكل غير متحكَّم فيه داخل المصفوفات المتداخلة من كائنات JSON. لنحاول قراءة الأعمدة الفرعية من عمود JSON متداخل:
Query
Response
يمكننا تجنّب كتابة أسماء الأعمدة الفرعية لـ Array(JSON) باستخدام صيغة خاصة:
Query
Response
يشير عدد [] بعد المسار إلى مستوى الـ Array. على سبيل المثال، سيُحوَّل json.path[][] إلى json.path.:Array(Array(JSON)) لنلقِ نظرة على المسارات والأنواع داخل Array(JSON):
Query
Response
لنقرأ الأعمدة الفرعية من عمود Array(JSON):
Query
Response
يمكننا أيضًا قراءة الأعمدة الفرعية الخاصة بالكائنات الفرعية من عمود JSON متداخل:
Query
Response

التعامل مع مفاتيح JSON ذات القيمة NULL

في تنفيذ JSON لدينا، يُعدّ null وغياب القيمة أمرين متكافئين:
Query
Response
وهذا يعني أنه يستحيل تحديد ما إذا كانت بيانات JSON الأصلية تتضمن مسارًا بقيمة NULL أم أنها لم تتضمنه أصلًا.

التعامل مع مفاتيح JSON التي تحتوي على نقاط

يخزّن عمود JSON داخليًا جميع المسارات والقيم بشكلٍ مُسطّح. وهذا يعني أنه، افتراضيًا، يُتعامل مع هذين الكائنين على أنهما متماثلان:
سيُخزَّن كلاهما داخليًا على شكل زوج يتكوّن من المسار a.b والقيمة 42. وأثناء تنسيق JSON، نُكوِّن دائمًا كائنات متداخلة استنادًا إلى أجزاء المسار المفصولة بنقطة:
Query
Response
كما ترى، أصبح JSON الأصلي {"a.b" : 42} مُنسقًا الآن على الشكل {"a" : {"b" : 42}}. ويؤدي هذا القيد أيضًا إلى تعذّر تحليل كائنات JSON صالحة مثل هذا:
Query
Response
إذا كنت تريد الاحتفاظ بالمفاتيح التي تحتوي على نقاط وتجنّب تنسيقها ككائنات متداخلة، فيمكنك تفعيل الإعداد json_type_escape_dots_in_keys (متاح بدءًا من الإصدار 25.8). في هذه الحالة، أثناء التحليل ستُحوَّل جميع النقاط في مفاتيح JSON عبر الإفلات إلى %2E، ثم يُزال عنها الإفلات مرة أخرى أثناء التنسيق.
Query
Response
Query
Response
لقراءة مفتاح يحتوي على نقطة مُفلَتة كعمود فرعي، يجب استخدام النقطة المُفلَتة في اسم العمود الفرعي:
Query
Response
ملاحظة: بسبب القيود في محلِّل المعرّفات والمُحلِّل، فإن العمود الفرعي json.`a.b` يعادل العمود الفرعي json.a.b ولن يقرأ المسار الذي يحتوي على نقطة مُفلَتة:
Query
Response
كذلك، إذا كنت تريد تحديد تلميح لمسار JSON يحتوي على مفاتيح تتضمن نقاطًا (أو استخدامه في قسمي SKIP/SKIP REGEX)، فعليك استخدام نقاط مسبوقة بمحرف الإفلات في التلميح:
Query
Response
Query
Response

قراءة نوع JSON من البيانات

تدعم جميع التنسيقات النصية (JSONEachRow, TSV, CSV, CustomSeparated, Values، إلخ) قراءة نوع JSON. أمثلة:
Query
Response
بالنسبة إلى التنسيقات النصية مثل CSV/TSV/إلخ، يُحلَّل JSON من سلسلة نصية تحتوي على كائن JSON:
Query
Response

الوصول إلى الحد الأقصى للمسارات الديناميكية داخل JSON

لا يستطيع نوع البيانات JSON تخزين سوى عدد محدود من المسارات داخليًا على هيئة أعمدة فرعية منفصلة. ويبلغ هذا الحد افتراضيًا 1024، لكن يمكنك تغييره في تعريف النوع باستخدام المعلمة max_dynamic_paths. عند الوصول إلى هذا الحد، ستُخزَّن جميع المسارات الجديدة المُدرجة في عمود JSON في بنية بيانات مشتركة واحدة. ولا يزال من الممكن قراءة هذه المسارات كأعمدة فرعية، لكن ذلك قد يكون أقل كفاءة (راجع القسم الخاص بالبيانات المشتركة). وهذا الحد ضروري لتجنّب وجود عدد هائل من الأعمدة الفرعية المختلفة، مما قد يجعل الجدول غير قابل للاستخدام. لنرَ ما يحدث عند الوصول إلى هذا الحد في بضعة سيناريوهات مختلفة.

الوصول إلى الحد أثناء تحليل البيانات

أثناء تحليل كائنات JSON في البيانات، وعند الوصول إلى الحد للكتلة الحالية من البيانات، ستُخزَّن جميع المسارات الجديدة في بنية بيانات مشتركة. يمكننا استخدام دالتي الاستبطان التاليتين JSONDynamicPaths وJSONSharedDataPaths:
Query
Response
كما نرى، بعد إدراج المسارين e و f.g، تم الوصول إلى الحد، وأُدرجا في بنية بيانات مشتركة.

أثناء دمج أجزاء البيانات في محركات جداول MergeTree

أثناء دمج عدة أجزاء بيانات في جدول MergeTree، قد يصل العمود JSON في جزء البيانات الناتج إلى حدّ المسارات الديناميكية، وبالتالي لن يتمكن من تخزين جميع المسارات من الأجزاء المصدرية كأعمدة فرعية. في هذه الحالة، يحدد ClickHouse المسارات التي ستبقى كأعمدة فرعية بعد الدمج، والمسارات التي ستُخزَّن في بنية البيانات المشتركة. في معظم الحالات، يحاول ClickHouse الإبقاء على المسارات التي تحتوي على أكبر عدد من القيم غير NULL، ونقل المسارات الأقل شيوعًا إلى بنية البيانات المشتركة. ومع ذلك، يعتمد هذا على طريقة التنفيذ. لنلقِ نظرة على مثال لهذا النوع من الدمج. لنبدأ أولًا بإنشاء جدول يحتوي على عمود JSON، وضبط حدّ المسارات الديناميكية على 3، ثم إدراج قيم تحتوي على 5 مسارات مختلفة:
Query
سينشئ كل insert جزء بيانات منفصلًا، بحيث يحتوي العمود JSON على مسار واحد:
Query
Response
الآن، لنَدمج جميع الأجزاء في جزء واحد ونرَ ماذا سيحدث:
Query
Response
كما نرى، احتفظ ClickHouse بالمسارات الأكثر تكرارًا a وb وc، ونقل المسارين d وe إلى بنية بيانات مشتركة.

بنية البيانات المشتركة

كما ذُكر في القسم السابق، عند بلوغ الحد max_dynamic_paths، تُخزَّن جميع المسارات الجديدة في بنية بيانات مشتركة واحدة. في هذا القسم، سنستعرض تفاصيل بنية البيانات المشتركة وكيفية قراءة الأعمدة الفرعية للمسارات منها. راجع قسم “دوال الاستبطان” للاطلاع على تفاصيل الدوال المستخدمة لفحص محتويات عمود JSON.

بنية البيانات المشتركة في الذاكرة

في الذاكرة، تكون بنية البيانات المشتركة مجرد عمود فرعي من النوع Map(String, String) يخزّن ربطًا بين مسار JSON مُسطَّح وقيمة مُرمَّزة ترميزًا ثنائيًا. ولاستخراج عمود فرعي لمسار منه، نمرّ ببساطة على جميع الصفوف في عمود Map هذا ونحاول العثور على المسار المطلوب وقيمه.

بنية البيانات المشتركة في أجزاء MergeTree

في جداول MergeTree، نُخزّن البيانات في أجزاء بيانات تحتفظ بكل شيء على القرص (محليًا أو عن بُعد). ويمكن تخزين البيانات على القرص بطريقة تختلف عن تخزينها في الذاكرة. يوجد حاليًا 3 تنسيقات serialization مختلفة لبنية البيانات المشتركة في أجزاء بيانات MergeTree: ‏map وmap_with_buckets وadvanced. يخضع إصدار serialization للتحكم عبر إعدادات MergeTree object_shared_data_serialization_version وobject_shared_data_serialization_version_for_zero_level_parts (جزء المستوى الصفري هو الجزء الذي يُنشأ عند insert البيانات إلى الجدول، وخلال merges تكون الأجزاء ذات مستويات أعلى). ملاحظة: لا يُدعَم تغيير serialization لبنية البيانات المشتركة إلا مع v3 object serialization version

Map

في إصدار التسلسل map، تُسلسَل البيانات المشتركة كعمود واحد من النوع Map(String, String)، تمامًا كما تُخزَّن في الذاكرة. لقراءة العمود الفرعي للمسار من هذا النوع من التسلسل، يقرأ ClickHouse عمود Map بالكامل ثم يستخرج المسار المطلوب في الذاكرة. يكون هذا التسلسل فعّالًا لكتابة البيانات وقراءة عمود JSON بالكامل، لكنه ليس فعّالًا لقراءة الأعمدة الفرعية للمسارات.

Map مع الحاويات

في إصدار تسلسل map_with_buckets، تُسلسَل البيانات المشتركة إلى N أعمدة (“حاويات”) من النوع Map(String, String). ويحتوي كل bucket من هذه الحاويات على مجموعة فرعية فقط من المسارات. لقراءة عمود فرعي لمسار من هذا النوع من التسلسل، يقرأ ClickHouse عمود Map بالكامل من bucket واحد ثم يستخرج المسار المطلوب في الذاكرة. يكون هذا التسلسل أقل كفاءة عند كتابة البيانات وقراءة عمود JSON بالكامل، لكنها أكثر كفاءة عند قراءة الأعمدة الفرعية للمسارات لأنها لا تقرأ البيانات إلا من الحاويات المطلوبة. يُتحكَّم في عدد الحاويات N بواسطة إعدادات MergeTree object_shared_data_buckets_for_compact_part (8 افتراضيًا) وobject_shared_data_buckets_for_wide_part (32 افتراضيًا). والحد الأقصى المسموح به لكلا الإعدادين هو 256.

متقدّم

في إصدار التسلسل advanced، تُسلسَل البيانات المشتركة ضمن بنية بيانات خاصة تُحسِّن إلى أقصى حد أداء قراءة الأعمدة الفرعية للمسارات، وذلك عبر تخزين بعض المعلومات الإضافية التي تتيح قراءة بيانات المسارات المطلوبة فقط. كما يدعم هذا التسلسل أيضًا الحاويات، بحيث تحتوي كل حاوية على مجموعة فرعية فقط من المسارات. يُعد هذا التسلسل غير فعّال نسبيًا عند كتابة البيانات (لذلك لا يُنصح باستخدامه مع الأجزاء ذات المستوى الصفري)، كما أن قراءة عمود JSON بالكامل أقل كفاءةً قليلًا مقارنةً بتسلسل map، لكنه فعّال جدًا عند قراءة الأعمدة الفرعية للمسارات. ملاحظة: بسبب تخزين بعض المعلومات الإضافية داخل بنية البيانات، يكون حجم التخزين على القرص أكبر مع هذا التسلسل مقارنةً بتسلسلي map وmap_with_buckets. للاطلاع على نظرة عامة أكثر تفصيلًا حول تسلسلات البيانات المشتركة الجديدة وتفاصيل التنفيذ، اقرأ منشور المدونة.

التحكّم في عدد المسارات الديناميكية داخل JSON في أجزاء MergeTree

الطريقة الرئيسية لفرض حدّ على المسارات الديناميكية في JSON هي استخدام المَعلَمة max_dynamic_paths داخل تعريف النوع JSON. لكن تغيير max_dynamic_paths للأعمدة الحالية يتطلّب تشغيل ALTER TABLE <table> MODIFY COLUMN <column> JSON(max_dynamic_paths=K)، مما يبدأ عملية mutation في الخلفية تعيد كتابة جميع الأجزاء الموجودة. وقد تكون هذه العملية ثقيلة جدًا، ويمكن أن تؤثر في أداء الخادم حتى اكتمالها. ولتجنّب ذلك، يمكنك استخدام هذه الإعدادات الثلاثة التي تساعدك على تغيير حدّ المسارات الديناميكية في جداول MergeTree لأجزاء البيانات الجديدة:
  • merge_max_dynamic_subcolumns_in_wide_part - إعداد في MergeTree يحدّ من عدد الأعمدة الفرعية الديناميكية لكل عمود JSON أثناء merge إلى جزء بيانات Wide.
  • merge_max_dynamic_subcolumns_in_compact_part - إعداد في MergeTree يحدّ من عدد الأعمدة الفرعية الديناميكية لكل عمود JSON أثناء merge إلى جزء بيانات Compact.
  • max_dynamic_subcolumns_in_json_type_parsing - إعداد session يحدّ من عدد الأعمدة الفرعية الديناميكية لكل عمود JSON أثناء parsing بيانات JSON إلى عمود JSON.
ملاحظة: لا يمكن أن يتجاوز حدّ المسارات الديناميكية القيمة المحددة في المَعلَمة max_dynamic_paths، حتى إذا كانت قيم الإعدادات المذكورة أعلى.

دوال الاستبطان

توجد عدة دوال تساعد على فحص محتوى عمود JSON: أمثلة لنفحص محتوى مجموعة بيانات GH Archive ليوم 2020-01-01:
Query
Response
Query
Response

ALTER MODIFY COLUMN إلى النوع JSON

يمكن تعديل جدول موجود وتغيير نوع العمود إلى النوع JSON الجديد. حاليًا، لا يُدعَم ALTER إلا عند التحويل من النوع String. مثال
Query
Response

تلميحات الأنواع الكسولة (تجريبية)

هذه الميزة تجريبية، وتتطلب أن يكون الإعداد allow_experimental_json_lazy_type_hints مُمكّنًا.
عند إضافة تلميحات الأنواع أو تعديلها في عمود JSON باستخدام ALTER TABLE ... MODIFY COLUMN، يعيد ClickHouse عادةً كتابة جميع أجزاء البيانات لتطبيق تلميحات الأنواع الجديدة وتخزينها فعليًا. وقد تكون هذه العملية باهظة التكلفة جدًا للجداول التي تحتوي على كميات كبيرة من البيانات التاريخية (مئات التيرابايتات). تتيح تلميحات الأنواع الكسولة إضافة تلميحات الأنواع كعملية تقتصر على البيانات الوصفية فقط، من دون إعادة كتابة البيانات الحالية:
  • الأجزاء القديمة: تُطبَّق تلميحات الأنواع وقت الاستعلام عبر التحويل من Dynamic إلى النوع المشار إليه
  • الأجزاء الجديدة: تُطبَّق تلميحات الأنواع وتُخزَّن فعليًا أثناء عمليات INSERT
  • عمليات الدمج: تُطبَّق تلميحات الأنواع وتُخزَّن فعليًا عند دمج الأجزاء
وهذا يعني أنه يمكنك إضافة تلميحات الأنواع فورًا، وستُحوَّل البيانات تدريجيًا مع حدوث عمليات الدمج العادية في الخلفية.

تمكين تلميحات الأنواع الكسولة

مثال

Query
Response

التحقق من عدم حدوث أي mutation

يمكنك التحقق من أن ALTER قد اكتملت من دون حدوث mutation عبر فحص جدول system.mutations:
عند تفعيل تلميحات الأنواع المؤجَّلة، لا يُرجع هذا الاستعلام أي صفوف، مما يؤكد أن العملية اقتصرت على البيانات الوصفية فقط.

تجسيد تلميح الأنواع

لتجسيد تلميح الأنواع في البيانات الموجودة، يمكنك إما:
  1. انتظار عمليات الدمج في الخلفية: سيعمل ClickHouse تلقائيًا على تجسيد تلميح الأنواع عند دمج الأجزاء
  2. فرض الدمج: استخدم OPTIMIZE TABLE test_lazy FINAL لدمج جميع الأجزاء فورًا
  3. إعادة كتابة الأجزاء: استخدم ALTER TABLE test_lazy REWRITE PARTS لإعادة كتابة الأجزاء باستخدام البيانات الوصفية الجديدة

القيود

  • هذه الميزة تجريبية وقد تتغير في الإصدارات المستقبلية
  • قد يترتب على تحويل الأنواع وقت الاستعلام عبء كبير على الأداء مقارنةً بالأنواع المُخزَّنة مسبقًا، خاصةً مع كائنات JSON الكبيرة
  • لا تنطبق هذه الميزة إلا عند تعديل typed_paths (تلميحات الأنواع)؛ أما معلمات JSON الأخرى مثل max_dynamic_paths أو SKIP أو SKIP REGEXP فما تزال تتطلب إجراء mutations

مقارنة قيم نوع JSON

تُقارَن كائنات JSON بصورة مماثلة لـ Maps. على سبيل المثال:
Query
Response
ملاحظة: إذا احتوى مساران على قيم من أنواع بيانات مختلفة، فتُقارَن وفقًا لـقاعدة المقارنة الخاصة بنوع البيانات Variant.

فهارس تخطي البيانات في JSON

يمكن استخدام فهارس تخطي البيانات مع أعمدة JSON بثلاث طرق:
  1. فهارس لأعمدة فرعية محددة — أنشئ فهرس تخطٍّ قياسيًا لمسار JSON معروف، تمامًا كما تفعل مع عمود عادي. يفهرس هذا القيم الموجودة في ذلك المسار.
  2. فهارس قائمة على المسار باستخدام JSONAllPaths — فهرسة مجموعة المسارات الموجودة في كل حبيبة لتخطي الحبيبات التي يستحيل أن تحتوي على المسار المطلوب في الاستعلام.
  3. فهارس قائمة على القيم باستخدام JSONAllValues — فهرسة جميع القيم عبر كل مسارات JSON باستخدام فهرس نصي لتسريع البحث النصي الكامل في أي عمود JSON فرعي باستخدام فهرس واحد.

فهارس على أعمدة فرعية محددة

يمكنك إنشاء فهرس تخطٍ على أي عمود فرعي في JSON باستخدام الصياغة نفسها المستخدمة مع الأعمدة العادية. يعمل أي نوع فهرس مدعوم (minmax، set، bloom_filter، tokenbf_v1، ngrambf_v1، إلخ). هناك طريقتان للإشارة إلى عمود فرعي في JSON داخل تعبير الفهرس:
  • مسار محدد النوع مُعلَن في تلميح الأنواع لـ JSON — يُوصَل إليه بالاسم مباشرةً: json.a.
  • مسار ديناميكي مع تحويل نوع صريح — استخدم صياغة التحويل ::: json.b::String.
يمكنك أيضًا استخدام تعبيرات تجمع بين عدة أعمدة فرعية، على سبيل المثال json.a || json.b::String.

مثال

Query
يُضيّق فهرس minmax على العمود الفرعي data.sensor_id ذي النوع المحدد نطاق الفحص ليقتصر على الحبيبات المطابقة:
Query
Response
يعمل فهرس bloom_filter أيضًا على العمود الفرعي المُحوَّل data.location::String:
Query
Response

فهارس قائمة على المسارات باستخدام JSONAllPaths

يمكن أيضًا إنشاء فهارس تخطي البيانات على أعمدة JSON باستخدام الدالة JSONAllPaths. ويعمل ذلك بطريقة مشابهة لإنشاء فهارس التخطي على أعمدة Map عبر mapKeys — إذ يخزّن الفهرس مجموعة مسارات JSON الموجودة في كل حبيبة، ويستخدمها لتخطي الحبيبات التي لا يمكن أن تحتوي على المسار المطلوب في الاستعلام.

أنواع الفهارس المدعومة

يمكن استخدام JSONAllPaths مع أنواع فهارس التخطي التالية:
  • bloom_filter — يدعم equals وin وIS NOT NULL.
  • tokenbf_v1 — يدعم equals وIS NOT NULL.
  • ngrambf_v1 — يدعم equals وIS NOT NULL.
  • text (فهرس مقلوب) — يدعم equals وin وIS NOT NULL.

مثال

Query
يمكنك استخدام EXPLAIN indexes = 1 للتحقق من استخدام فهرس التخطي. عندما يكون المسار موجودًا في جزء واحد فقط، يتجاوز الفهرس الجزء الآخر:
Query
Response
عندما لا يكون المسار موجودًا في أي جزء، تُتخطّى جميع الأجزاء والحبيبات:
Query
Response
يستخدم IS NOT NULL أيضًا الفهرس — إذ يتجاوز الحبيبات التي يكون فيها المسار غير موجود (لأن القيمة ستكون NULL):
Query
Response

كيف يعمل

يُنتج التعبير JSONAllPaths(json_column) قيمة Array(String) تحتوي على جميع المسارات الموجودة في قيمة JSON. يخزّن فهرس التخطي سلاسل المسارات هذه في بنية البيانات الخاصة به (bloom filter أو inverted index). عندما يطبّق الاستعلام عامل تصفية على json.some.path، يتحقق الفهرس مما إذا كانت السلسلة "some.path" موجودة في الفهرس لكل حبيبة، ويتجاوز الحبيبات التي لا تحتوي عليها.

الأمان عند غياب المسارات

عندما يكون مسار JSON غائبًا عن حبيبة، فإن قيمة العمود الفرعي تكون:
  • NULL مع النوع Dynamic (مثل json.path) والأعمدة الفرعية من النوع Nullable (مثل json.path.:Int64) — تُرجِع المقارنات مع NULL دائمًا false، لذا يكون skipping آمنًا.
  • القيمة الافتراضية للنوع في تعبيرات CAST غير Nullable (مثل json.path::Int64 الذي ينتج 0 عند غياب المسار) — لا يكون skipping آمنًا إلا عندما تختلف القيمة المُقارَنة عن القيمة الافتراضية. ويتولى الفهرس هذا التمييز تلقائيًا.

البحث النصي الكامل باستخدام JSONAllValues

يمكن استخدام الفهارس النصية لتسريع البحث النصي الكامل في أعمدة JSON من خلال الدالة JSONAllValues. تُرجع JSONAllValues جميع القيم من عمود JSON بصيغة Array(String)، ويمكن فهرستها باستخدام فهرس نصي. ويغطي فهرس واحد على JSONAllValues(json_column) جميع مسارات JSON، مما يتيح البحث النصي الكامل في أي عمود فرعي دون الحاجة إلى إنشاء فهارس منفصلة لكل مسار. راجع الفهارس المستندة إلى القيم باستخدام JSONAllValues في توثيق الفهارس النصية للاطلاع على التفاصيل والأمثلة.

نصائح لتحسين استخدام نوع JSON

قبل إنشاء عمود JSON وتحميل البيانات فيه، ضع النصائح التالية في اعتبارك:
  • افحص بياناتك وحدّد أكبر عدد ممكن من تلميحات المسارات مع أنواعها. فهذا يجعل التخزين والقراءة أكثر كفاءة بدرجة كبيرة.
  • فكّر في المسارات التي ستحتاج إليها والمسارات التي لن تحتاج إليها أبدًا. حدّد المسارات التي لن تحتاج إليها في قسم SKIP، وفي قسم SKIP REGEXP عند الحاجة. سيؤدي ذلك إلى تحسين التخزين.
  • لا تضبط المعلَمة max_dynamic_paths على قيم مرتفعة جدًا، لأن ذلك قد يقلّل من كفاءة التخزين والقراءة. ورغم أن ذلك يعتمد بدرجة كبيرة على معلمات النظام مثل الذاكرة وCPU وما إلى ذلك، فإن القاعدة العامة هي ألا تضبط max_dynamic_paths على قيمة تتجاوز 10 000 لتخزين نظام الملفات المحلي و1024 لتخزين نظام الملفات البعيد.

للمزيد من القراءة

آخر تعديل في ٢ يوليو ٢٠٢٦