الانتقال إلى المحتوى الرئيسي
يوفّر ClickHouse دعمًا كاملًا لـ JOIN، مع مجموعة واسعة من خوارزميات الربط. ولتحقيق أقصى أداء، نوصي باتباع اقتراحات تحسين JOIN الواردة في هذا الدليل.
  • للحصول على أفضل أداء، ينبغي أن تسعى إلى تقليل عدد عمليات JOIN في الاستعلامات، خاصةً في أحمال العمل التحليلية في الوقت الفعلي التي تتطلب أداءً بزمن استجابة في حدود المللي ثانية. استهدف حدًا أقصى من 3 إلى 4 عمليات JOIN في الاستعلام الواحد. ونفصّل عددًا من التغييرات لتقليل عمليات الربط في قسم نمذجة البيانات، بما في ذلك إلغاء التطبيع، وDictionaries، والعروض المادية.
  • اعتبارًا من ClickHouse 24.12، يعيد مُخطِّط الاستعلام تلقائيًا ترتيب عمليات الربط بين جدولين بحيث يضع الجدول الأصغر على الجانب الأيمن لتحقيق أفضل أداء. وفي الإصدار 25.9، تم توسيع ذلك لتحسين ترتيب عمليات الربط في الاستعلامات التي تربط بين ثلاثة جداول أو أكثر.
  • إذا كان استعلامك يتطلب ربطًا مباشرًا، أي LEFT ANY JOIN، كما هو موضح أدناه، فنوصي باستخدام Dictionaries حيثما أمكن.
  • إذا كنت تُجري عمليات ربط داخلية، فغالبًا ما يكون من الأفضل كتابتها على هيئة استعلامات فرعية باستخدام العبارة IN. انظر إلى الاستعلامات التالية، وهي متكافئة وظيفيًا. فكلاهما يعثر على عدد posts التي لا تذكر ClickHouse في السؤال، لكنها تذكره في comments.
SELECT count()
FROM stackoverflow.posts AS p
ANY INNER `JOIN` stackoverflow.comments AS c ON p.Id = c.PostId
WHERE (p.Title != '') AND (p.Title NOT ILIKE '%clickhouse%') AND (p.Body NOT ILIKE '%clickhouse%') AND (c.Text ILIKE '%clickhouse%')
┌─count()─┐
│       86 │
└─────────┘

1 row in set. Elapsed: 8.209 sec. Processed 150.20 million rows, 56.05 GB (18.30 million rows/s., 6.83 GB/s.)
Peak memory usage: 1.23 GiB.
لاحظ أننا نستخدم ANY INNER JOIN بدلًا من INNER JOIN فقط، لأننا لا نريد الناتج الديكارتي؛ أي نريد مطابقة واحدة فقط لكل منشور. يمكن إعادة كتابة هذا الـ JOIN باستخدام استعلام فرعي، مما يحسّن الأداء بشكل كبير:
SELECT count()
FROM stackoverflow.posts
WHERE (Title != '') AND (Title NOT ILIKE '%clickhouse%') AND (Body NOT ILIKE '%clickhouse%') AND (Id IN (
        SELECT PostId
        FROM stackoverflow.comments
        WHERE Text ILIKE '%clickhouse%'
))
┌─count()─┐
│       86 │
└─────────┘

1 row in set. Elapsed: 2.284 sec. Processed 150.20 million rows, 16.61 GB (65.76 million rows/s., 7.27 GB/s.)
Peak memory usage: 323.52 MiB.
على الرغم من أن ClickHouse يحاول تمرير الشروط إلى جميع بنود JOIN والاستعلامات الفرعية، فإننا نوصي المستخدمين دائمًا بتطبيق الشروط يدويًا على جميع البنود الفرعية حيثما أمكن، لتقليل حجم البيانات المرسلة إلى JOIN إلى أدنى حد. تأمل المثال التالي، حيث نريد حساب عدد الأصوات المؤيدة للمنشورات المتعلقة بـ Java منذ عام 2020. يكتمل استعلام ساذج، مع وضع الجدول الأكبر على الجانب الأيسر، في 56 ثانية:
SELECT countIf(VoteTypeId = 2) AS upvotes
FROM stackoverflow.posts AS p
INNER JOIN stackoverflow.votes AS v ON p.Id = v.PostId
WHERE has(arrayFilter(t -> (t != ''), splitByChar('|', p.Tags)), 'java') AND (p.CreationDate >= '2020-01-01')
┌─upvotes─┐
│  261915 │
└─────────┘

1 row in set. Elapsed: 56.642 sec. Processed 252.30 million rows, 1.62 GB (4.45 million rows/s., 28.60 MB/s.)
تُحسِّن إعادة ترتيب هذا الـ JOIN الأداء بشكل كبير ليصل إلى 1.5 ثانية:
SELECT countIf(VoteTypeId = 2) AS upvotes
FROM stackoverflow.votes AS v
INNER JOIN stackoverflow.posts AS p ON v.PostId = p.Id
WHERE has(arrayFilter(t -> (t != ''), splitByChar('|', p.Tags)), 'java') AND (p.CreationDate >= '2020-01-01')
┌─upvotes─┐
│  261915 │
└─────────┘

1 row in set. Elapsed: 1.519 sec. Processed 252.30 million rows, 1.62 GB (166.06 million rows/s., 1.07 GB/s.)
تؤدي إضافة شرط تصفية إلى الجدول الأيسر إلى تحسين الأداء أكثر، ليصل إلى 0.5 ثانية.
SELECT countIf(VoteTypeId = 2) AS upvotes
FROM stackoverflow.votes AS v
INNER JOIN stackoverflow.posts AS p ON v.PostId = p.Id
WHERE has(arrayFilter(t -> (t != ''), splitByChar('|', p.Tags)), 'java') AND (p.CreationDate >= '2020-01-01') AND (v.CreationDate >= '2020-01-01')
┌─upvotes─┐
│  261915 │
└─────────┘

1 row in set. Elapsed: 0.597 sec. Processed 81.14 million rows, 1.31 GB (135.82 million rows/s., 2.19 GB/s.)
Peak memory usage: 249.42 MiB.
يمكن تحسين هذا الاستعلام بدرجة أكبر بنقل INNER JOIN إلى استعلام فرعي، كما أُشير سابقًا، مع الإبقاء على شرط التصفية في كلٍّ من الاستعلام الخارجي والداخلي.
SELECT count() AS upvotes
FROM stackoverflow.votes
WHERE (VoteTypeId = 2) AND (PostId IN (
        SELECT Id
        FROM stackoverflow.posts
        WHERE (CreationDate >= '2020-01-01') AND has(arrayFilter(t -> (t != ''), splitByChar('|', Tags)), 'java')
))
┌─upvotes─┐
│  261915 │
└─────────┘

1 row in set. Elapsed: 0.383 sec. Processed 99.64 million rows, 804.55 MB (259.85 million rows/s., 2.10 GB/s.)
Peak memory usage: 250.66 MiB.

اختيار خوارزمية JOIN

يدعم ClickHouse عددًا من خوارزميات JOIN. وعادةً ما تنطوي هذه الخوارزميات على مفاضلة بين استخدام الذاكرة والأداء. يقدّم ما يلي نظرة عامة على خوارزميات JOIN في ClickHouse استنادًا إلى استهلاكها النسبي للذاكرة ووقت التنفيذ:

تحدّد هذه الخوارزميات كيفية تخطيط استعلام JOIN وتنفيذه. وبشكل افتراضي، يستخدم ClickHouse خوارزمية direct أو hash JOIN استنادًا إلى نوع JOIN المستخدم وقيمة strictness ومحرك الجداول التي يجري ضمّها. وبدلًا من ذلك، يمكن تهيئة ClickHouse لاختيار خوارزمية JOIN بشكل تكيّفي وتغييرها ديناميكيًا في وقت التشغيل، بحسب توافر الموارد واستخدامها: فعندما تكون join_algorithm=auto، يجرّب ClickHouse أولًا خوارزمية hash JOIN، وإذا تم تجاوز حد الذاكرة الخاص بهذه الخوارزمية، تُستبدل الخوارزمية أثناء التنفيذ بـ partial merge JOIN. ويمكنك معرفة الخوارزمية التي تم اختيارها عبر trace logging. كما يتيح لك ClickHouse أيضًا تحديد خوارزمية JOIN المطلوبة بنفسك من خلال الإعداد join_algorithm. تُعرض أدناه أنواع JOIN المدعومة لكل خوارزمية JOIN، وينبغي أخذها في الاعتبار قبل إجراء التحسين:

يمكن العثور على وصف تفصيلي كامل لكل خوارزمية JOIN هنا، بما في ذلك مزاياها وعيوبها وخصائص التوسّع الخاصة بها. يعتمد اختيار خوارزميات JOIN المناسبة على ما إذا كنت تسعى إلى التحسين من حيث الذاكرة أو الأداء.

تحسين أداء JOIN

إذا كان مقياس التحسين الأساسي لديك هو الأداء، وكنت تبحث عن تنفيذ عملية الـ JOIN بأسرع ما يمكن، فيمكنك استخدام شجرة القرار التالية لاختيار خوارزمية الـ JOIN المناسبة:

  • (1) إذا كان يمكن تحميل بيانات الجدول الموجود على الجانب الأيمن مسبقًا إلى بنية بيانات مفتاح-قيمة داخل الذاكرة منخفضة الكمون، مثل القاموس، وكان مفتاح الـ JOIN يطابق سمة المفتاح في مخزن المفتاح-القيمة الأساسي، وكانت دلالات LEFT ANY JOIN كافية، فإن direct JOIN تكون مناسبة وتوفّر أسرع نهج.
  • (2) إذا كان الترتيب المادي للصفوف في جدولك يطابق ترتيب فرز مفتاح الـ JOIN، فالأمر يعتمد على الحالة. في هذه الحالة، تتخطى full sorting merge JOIN مرحلة الفرز، مما يؤدي إلى خفض كبير في استخدام الذاكرة، وقد يوفّر أيضًا، بحسب حجم البيانات وتوزيع قيم أعمدة مفتاح الـ JOIN، زمن تنفيذ أسرع من بعض خوارزميات hash JOIN.
  • (3) إذا كان الجدول الأيمن يتّسع في الذاكرة، حتى مع العبء الإضافي على استخدام الذاكرة في parallel hash JOIN، فقد تكون هذه الخوارزمية أو hash JOIN أسرع. ويعتمد ذلك على حجم البيانات وأنواع البيانات وتوزيع قيم أعمدة مفتاح الـ JOIN.
  • (4) إذا كان الجدول الأيمن لا يتّسع في الذاكرة، فالأمر يعتمد مرة أخرى. يوفّر ClickHouse ثلاث خوارزميات JOIN غير مقيّدة بالذاكرة. وجميعها تقوم مؤقتًا بترحيل البيانات إلى القرص. تتطلّب full sorting merge JOIN وpartial merge JOIN فرز البيانات مسبقًا. أما Grace hash JOIN فتبني جداول hash من البيانات بدلًا من ذلك. وبحسب حجم البيانات وأنواع البيانات وتوزيع قيم أعمدة مفتاح الـ JOIN، قد توجد حالات يكون فيها بناء جداول hash من البيانات أسرع من فرز البيانات، والعكس صحيح.
تم تحسين partial merge JOIN لتقليل استخدام الذاكرة إلى أدنى حد عند تنفيذ JOIN على الجداول الكبيرة، لكن ذلك يأتي على حساب سرعة الـ JOIN، التي تكون بطيئة نسبيًا. ويظهر ذلك بشكل خاص عندما لا يطابق الترتيب المادي لصفوف الجدول الأيسر ترتيب فرز مفتاح الـ JOIN. تُعد Grace hash JOIN الأكثر مرونة بين خوارزميات الـ JOIN الثلاث غير المقيّدة بالذاكرة، وتوفّر تحكمًا جيدًا في الموازنة بين استخدام الذاكرة وسرعة الـ JOIN من خلال الإعداد grace_hash_join_initial_buckets. وبحسب حجم البيانات، قد تكون grace hash أسرع أو أبطأ من خوارزمية partial merge، وذلك عندما يُختار عدد buckets بحيث يكون استخدام الذاكرة في كلتا الخوارزميتين متقاربًا تقريبًا. وعندما يُضبط استخدام الذاكرة في grace hash JOIN ليكون متقاربًا تقريبًا مع استخدام الذاكرة في full sorting merge، كانت full sorting merge دائمًا أسرع في اختباراتنا. يعتمد تحديد أي من الخوارزميات الثلاث غير المقيّدة بالذاكرة هو الأسرع على حجم البيانات وأنواع البيانات وتوزيع قيم أعمدة مفتاح الـ JOIN. ومن الأفضل دائمًا إجراء بعض اختبارات الأداء باستخدام أحجام بيانات وبيانات واقعية لتحديد الخوارزمية الأسرع.

التحسين من حيث الذاكرة

إذا كنت تريد تحسين عملية JOIN لتحقيق أقل استهلاك ممكن للذاكرة بدلًا من أسرع وقت تنفيذ، فيمكنك استخدام شجرة القرار التالية بدلًا من ذلك:

  • (1) إذا كان الترتيب الفعلي للصفوف في table لديك يطابق ترتيب فرز مفتاح الـ JOIN، فإن استهلاك الذاكرة في full sorting merge JOIN يكون عند أدنى مستوى ممكن. وتتمثل فائدة إضافية في الحصول على سرعة JOIN جيدة لأن مرحلة الفرز تكون معطلة.
  • (2) يمكن ضبط grace hash JOIN لتحقيق استهلاك منخفض جدًا للذاكرة من خلال تهيئة عدد كبير من buckets، ولكن على حساب سرعة JOIN. ويستخدم partial merge JOIN عمدًا مقدارًا قليلًا من الذاكرة الرئيسية. وبوجه عام، يستخدم full sorting merge JOIN مع تفعيل الفرز الخارجي ذاكرةً أكبر من partial merge JOIN (على افتراض أن ترتيب الصفوف لا يطابق ترتيب فرز المفتاح)، مقابل ميزة تحسن كبير في وقت تنفيذ JOIN.
بالنسبة إلى المستخدمين الذين يحتاجون إلى مزيد من التفاصيل حول ما سبق، نوصي بسلسلة المدونات التالية.
آخر تعديل في ٢٩ يونيو ٢٠٢٦