Map(K, V) أزواج المفتاح-القيمة.
بخلاف قواعد البيانات الأخرى، لا تكون المفاتيح في الخرائط فريدة في ClickHouse، أي يمكن أن تحتوي الخريطة على عنصرين بالمفتاح نفسه.
(ويعود ذلك إلى أن الخرائط تُنفَّذ داخليًا على هيئة Array(Tuple(K, V)).)
يمكنك استخدام الصياغة m[k] للحصول على قيمة المفتاح k في الخريطة m.
كذلك، تقوم m[k] بمسح الخريطة، أي إن زمن تنفيذ العملية يزداد خطيًا مع حجم الخريطة.
المعلمات
K— نوع مفاتيح Map. يمكن أن يكون أي نوع باستثناء Nullable وLowCardinality المتداخل مع أنواع Nullable.V— نوع قيم Map. يمكن أن يكون أي نوع.
Query
key2:
Query
Response
k موجودًا في الـ Map، فإن m[k] تُرجع القيمة الافتراضية لنوع القيمة، مثل 0 لأنواع الأعداد الصحيحة و '' لأنواع السلاسل النصية.
للتحقق مما إذا كان مفتاح ما موجودًا في Map، يمكنك استخدام الدالة mapContains.
Query
Response
تحويل Tuple إلى Map
Tuple() إلى قيم من النوع Map() باستخدام الدالة CAST:
مثال
Query
Response
قراءة الأعمدة الفرعية في Map
keys وvalues.
مثال
Query
Response
التسلسل المُقسَّم إلى buckets لـ Map في MergeTree
Map في MergeTree كتدفّق واحد من Array(Tuple(K, V)).
تتطلّب قراءة مفتاح واحد باستخدام m['key'] فحص العمود بأكمله — أي كل زوج مفتاح-قيمة في كل صف — حتى إذا كان المطلوب مفتاحًا واحدًا فقط.
وبالنسبة إلى maps التي تحتوي على عدد كبير من المفاتيح المميّزة، يصبح ذلك عنق زجاجة.
يُقسِّم التسلسل المُقسَّم إلى buckets (with_buckets) أزواج المفتاح-القيمة إلى عدة تدفّقات فرعية مستقلة (buckets) عبر تطبيق hash على المفتاح.
وعندما يصل الاستعلام إلى m['key']، لا يُقرأ من القرص إلا الـ bucket الذي يحتوي على ذلك المفتاح، مع تخطّي جميع الـ buckets الأخرى.
تمكين التسلسل بالتقسيم إلى دلاء
basic للأجزاء من المستوى الصفري (التي أُنشئت أثناء INSERT) واستخدام with_buckets فقط للأجزاء الناتجة عن الدمج:
كيف يعمل
with_buckets:
- يُحسَب متوسط عدد المفاتيح لكل صف من إحصاءات block.
- يُحدَّد عدد الحاويات وفقًا للاستراتيجية المُعدّة (راجع الإعدادات).
- يُسنَد كل زوج مفتاح-قيمة إلى حاوية عن طريق تطبيق تجزئة على المفتاح:
bucket = hash(key) % num_buckets. - يُخزَّن كل حاوية باعتباره تدفقًا فرعيًا مستقلًا له مفاتيحه وقيمه وoffsets الخاصة به.
- يسجّل تدفق البيانات الوصفية
buckets_infoعدد الحاويات والإحصاءات.
m['key'])، يعيد المُحسِّن كتابة expression إلى subcolumn للمفتاح (m.key_<serialized_key>).
ثم تحسب طبقة التسلسل الحاوية التي ينتمي إليها المفتاح المطلوب، ولا تقرأ من disk سوى تلك الحاوية الواحدة.
عند قراءة map بالكامل (مثل SELECT m)، تُقرأ جميع الحاويات ويُعاد تجميعها لتكوين map الأصلي. ويكون ذلك أبطأ من تسلسل basic بسبب overhead الناتج عن قراءة عدة تدفقات فرعية ودمجها.
اعتبارًا من الإصدار 26.8، يحافظ تسلسل
with_buckets على ترتيب المفاتيح الأصلي: يسجّل تدفق فرعي إضافي باسم bucket_indexes الـ حاوية الذي أُخذ منه كل زوج مفتاح-قيمة، بحيث يُعاد تجميع map بالترتيب الذي كُتب به بدلًا من ترتيب الحاويات.لا تحتوي الأجزاء التي كتبتها الإصدارات السابقة على ذلك التدفق الفرعي. ولا تزال maps الخاصة بها تُعاد تجميعها بحسب ترتيب الحاويات، ولا يمكن استعادة ترتيب المفاتيح الأصلي لها لأنه لم يُخزَّن مطلقًا على disk — إذ إن إعادة كتابة مثل هذا الجزء (بدمج أو باستخدام OPTIMIZE FINAL) تثبّت ترتيب الحاويات الحالي بدلًا من استعادة ترتيب الإدراج. أما مع تسلسل basic، فكان ترتيب المفاتيح في maps المُدرجة محفوظًا دائمًا.basic وwith_buckets في table نفسها، وتُدمَج هذه الأجزاء بشفافية.
الإعدادات
مقايضات الأداء
with_buckets على الأداء مقارنةً بتنسيق التسلسل basic عند أحجام مختلفة لـ Map (من 10 إلى 10,000 مفتاح لكل صف). وقد حُدِّد عدد الحاويات باستخدام استراتيجية sqrt بحد أقصى 32. وتعتمد الأرقام الدقيقة على أنواع المفاتيح/القيم، وتوزيع البيانات، والأجهزة.
التوصيات
- الخرائط الصغيرة (أقل من 32 مفتاحًا في المتوسط): أبقِ على التسلسل
basic. لا يبرَّر العبء الإضافي للتقسيم إلى buckets مع الخرائط الصغيرة. وتفرض القيمة الافتراضيةmap_buckets_min_avg_size = 32ذلك تلقائيًا. - الخرائط المتوسطة (32–100 مفتاح): استخدم
with_bucketsمع استراتيجيةsqrtإذا كانت الاستعلامات تصل كثيرًا إلى مفاتيح فردية. تتراوح زيادة السرعة بين 4x و8x لعملياتlookupعلى مفتاح واحد. - الخرائط الكبيرة (100+ مفتاح): استخدم
with_buckets. تكون عملياتlookupعلى مفتاح واحد أسرع بمقدار 16x إلى 49x. فكّر في استخدامmap_serialization_version_for_zero_level_parts = 'basic'للحفاظ على سرعةinsertقريبة من الخط الأساسي. - إذا كانت عمليات الفحص الكامل للخريطة هي المهيمنة على عبء العمل: أبقِ على
basic. يضيف bucketed serialization عبئًا إضافيًا يقارب 2x عند الفحص الكامل. - عبء عمل مختلط (بعض عمليات
lookupللمفاتيح، وبعض عمليات الفحص الكامل): استخدمwith_bucketsمع ضبط zero-level parts علىbasic. يقرأ تحسينPREWHEREالـ bucket ذي الصلة فقط من أجلfilter، ثم يقرأ الخريطة كاملةً فقط للصفوف المطابقة، مما يحقق زيادة صافية كبيرة في السرعة.
أساليب بديلة
Map المُقسَّم إلى مجموعات مناسبًا لسيناريو استخدامك، فهناك أسلوبان بديلان لتحسين أداء الوصول على مستوى المفاتيح:
استخدام نوع بيانات JSON
max_dynamic_paths فتُنقل إلى بنية بيانات مشتركة، والتي يمكنها استخدام التسلسل advanced لتحسين قراءة المسار الواحد. راجع منشور المدونة للحصول على نظرة عامة مفصلة على التسلسل advanced.
استخدم
JSON عندما تحتاج المفاتيح المختلفة إلى أنواع قيم مختلفة، أو عندما تختلف مجموعة المفاتيح بشكل كبير بين الصفوف، أو عندما تكون المفاتيح كثيرة الاستخدام معروفة مسبقًا ويمكن تعريفها كمسارات ذات أنواع محددة للوصول المباشر إلى العمود الفرعي.
التقسيم اليدوي إلى عدة أعمدة Map
Map واحدة يدويًا إلى عدة أعمدة بناءً على تجزئة المفتاح على مستوى التطبيق:
m{hash(key) % 4}. وأثناء الاستعلامات، اقرأ من العمود المحدد: m{hash('target_key') % 4}['target_key'].
تكون التجزئة اليدوية مفيدة عندما يكون الدمج العمودي مهمًا لتقليل استخدام الذاكرة أثناء عمليات الدمج في الجداول ذات الأعمدة الكثيرة، أو عندما يجب تثبيت عدد الأجزاء والتحكم فيه صراحةً. وفي معظم حالات الاستخدام، يكون التقسيم التلقائي إلى buckets أبسط وكافيًا.
راجع أيضًا
- الدالة map()
- الدالة CAST()
- المُركِّب -Map لنوع البيانات Map