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

اختيار استراتيجية التحديث

هناك نهجان أساسيان لتحديث البيانات في ClickHouse:
  1. استخدام محركات الجداول المتخصصة التي تتعامل مع التحديثات عبر عمليات الإدراج
  2. استخدام التحديثات التصريحية مثل عبارتي UPDATE ... SET أو ALTER TABLE ... UPDATE
ضمن كل فئة من الفئتين المذكورتين أعلاه، توجد عدة طرق لتحديث البيانات. ولكل طريقة مزاياها وخصائص أدائها، لذا ينبغي اختيار الطريقة المناسبة وفقًا لنموذج بياناتك وحجم البيانات التي تنوي تحديثها.

متى تستخدم محركات الجداول المتخصصة

تُعدّ محركات الجداول المتخصصة الخيار الأفضل عندما تكون لديك كميات كبيرة من التحديثات، أو تغييرات متكررة على مستوى الصفوف، أو تحتاج إلى معالجة دفق مستمر من أحداث التحديث والحذف. المحركات التي ستصادفها غالبًا هي:
المحركالبنيةمتى يُستخدم
ReplacingMergeTreeENGINE = ReplacingMergeTreeاستخدمه عند تحديث كميات كبيرة من البيانات. هذا المحرك مُحسَّن لإزالة تكرار البيانات أثناء عمليات الدمج.
CoalescingMergeTreeENGINE = CoalescingMergeTreeاستخدمه عندما تصل البيانات على هيئة أجزاء متفرقة وتحتاج إلى الدمج على مستوى العمود بدلًا من استبدال الصف بالكامل.
CollapsingMergeTreeENGINE = CollapsingMergeTree(Sign)استخدمه عند تحديث الصفوف الفردية بشكل متكرر، أو في الحالات التي تحتاج فيها إلى الاحتفاظ بأحدث حالة للكائنات التي تتغير بمرور الوقت. على سبيل المثال، تتبّع نشاط المستخدم أو إحصاءات المقالات.
وبما أن محركات الجداول من عائلة MergeTree تدمج أجزاء البيانات في الخلفية، فهي توفّر اتساقًا نهائيًا، ويجب استخدام الكلمة المفتاحية FINAL لضمان إزالة التكرار بشكل صحيح خلال هذه الفترة عند الاستعلام عن الجدول. توجد أيضًا أنواع محركات أخرى، لكن هذه هي الأكثر شيوعًا.

متى تستخدم التحديثات التصريحية

قد تكون عبارات UPDATE التصريحية أكثر بساطة في عمليات التحديث البسيطة من دون تعقيد إدارة منطق إزالة التكرار، لكنها تكون عمومًا أنسب لتحديث عدد أقل من الصفوف وبوتيرة أقل مقارنةً بالمحركات المتخصصة.
الطريقةالصياغةمتى تُستخدم
التحديثات خفيفة الوزنUPDATE [table] SET ... WHEREاستخدم هذا في معظم الحالات، وخصوصًا عند تنفيذ عمليات UPDATE صغيرة ومتكررة (حتى نحو 10% من الجدول) كجزء من تطبيقك أو سير العمل. على سبيل المثال، قد يرغب مستخدم في حذف سجل أحداثه، بينما تكون الأحداث موزعة ضمن جدول متعدد المستأجرين يضم عددًا كبيرًا من المستخدمين. ينشئ هذا النهج أجزاء تصحيح لتوفير ظهور فوري من دون إعادة كتابة أعمدة كاملة. ويضيف عبئًا إضافيًا إلى استعلامات SELECT، لكنه يوفر زمن استجابة يمكن التنبؤ به.
طفرة التحديثALTER TABLE [table] UPDATEاستخدم هذا عند تنفيذ إدارة بيانات على نطاق أوسع، وخصوصًا عندما يتوافق التحديث مع تقسيم الجدول. على سبيل المثال، إذا كنت بحاجة إلى تحديث عمود لكل الصفوف ضمن شهر واحد في جدول مُقسَّم حسب الشهر.

التحديثات باستخدام محركات الجداول المتخصصة

ReplacingMergeTree

يقوم ReplacingMergeTree بإزالة تكرار الصفوف التي لها مفتاح فرز واحد أثناء عمليات الدمج في الخلفية، مع الاحتفاظ بأحدث إصدار فقط.
CREATE TABLE posts
(
    Id UInt32,
    Title String,
    ViewCount UInt32,
    Version UInt32
)
ENGINE = ReplacingMergeTree(Version)
ORDER BY Id
يُعد هذا المحرك مثاليًا للتحديثات المتكررة جدًا على الصفوف الفردية عندما تُعرَّف التحديثات بواسطة مفتاح ثابت. وتُظهر اختبارات الأداء أنه قد يكون أسرع بما يصل إلى 4,700 مرة من عمليات mutations عند تحديث صف واحد. لتحديث صف، ما عليك سوى إدراج إصدار جديد بالقيم نفسها لمفتاح الفرز وبرقم إصدار أعلى. تُزال الإصدارات الأقدم أثناء عمليات الدمج في الخلفية. ونظرًا لأن إزالة التكرار ليست فورية (فهي لا تحدث إلا أثناء عمليات الدمج)، ينبغي استخدام المُعدِّل FINAL أو منطق استعلام مكافئ للحصول على نتائج صحيحة وخالية من التكرار. ويضيف المُعدِّل FINAL عبئًا إضافيًا على الاستعلام يتراوح بين 21% و550% بحسب البيانات. لا يمكن لـ ReplacingMergeTree تحديث قيم مفتاح الفرز. كما أنه يدعم عمود Deleted لعمليات الحذف المنطقي. اقرأ المزيد: دليل ReplacingMergeTree | مرجع ReplacingMergeTree

CoalescingMergeTree

يقوم CoalescingMergeTree بدمج السجلات المتفرقة من خلال الاحتفاظ بأحدث قيمة غير NULL لكل عمود أثناء عمليات الدمج. ويتيح ذلك تنفيذ عمليات إدراج أو تحديث على مستوى الأعمدة بدلًا من استبدال الصف بالكامل.
CREATE TABLE electric_vehicle_state
(
    vin String, -- vehicle identification number
    last_update DateTime64 Materialized now64(), -- optional (used with argMax)
    battery_level Nullable(UInt8), -- in %
    lat Nullable(Float64), -- latitude (°)
    lon Nullable(Float64), -- longitude (°)
    firmware_version Nullable(String),
    cabin_temperature Nullable(Float32), -- in °C
    speed_kmh Nullable(Float32) -- from sensor
)
ENGINE = CoalescingMergeTree
ORDER BY vin;
صُمم هذا المحرك لسيناريوهات تصل فيها البيانات على شكل أجزاء من مصادر متعددة، أو تُملأ فيها أعمدة مختلفة في أوقات مختلفة. وتشمل حالات الاستخدام الشائعة بيانات القياس عن بُعد لإنترنت الأشياء من الأنظمة الفرعية المتجزئة، وإثراء ملفات تعريف المستخدمين، ومسارات ETL ذات الأبعاد المتأخرة. عند دمج الصفوف التي لها مفتاح الفرز نفسه، يحتفظ CoalescingMergeTree بأحدث قيمة غير NULL لكل عمود بدلًا من استبدال الصف بأكمله. يجب أن تكون الأعمدة غير المفتاحية من النوع Nullable لكي يعمل ذلك كما هو مقصود. وكما هو الحال مع ReplacingMergeTree، استخدم FINAL للحصول على نتائج صحيحة ومُوحَّدة. هذا المحرك متاح ابتداءً من ClickHouse 25.6. اقرأ المزيد: CoalescingMergeTree

CollapsingMergeTree

انطلاقًا من فكرة أن التحديثات مكلفة، لكن يمكن الاستفادة من عمليات الإدراج لتنفيذها، يستخدم CollapsingMergeTree عمود Sign لإخبار ClickHouse بكيفية التعامل مع الصفوف أثناء عمليات الدمج. إذا أُدرجت القيمة -1 في عمود Sign، فسيُطوى الصف (أي يُحذف) عند إقرانه بصف مطابق ذي القيمة +1. تُحدَّد الصفوف المطلوب تحديثها استنادًا إلى مفتاح الفرز المستخدم في عبارة ORDER BY عند إنشاء الجدول.
CREATE TABLE user_activity
(
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID

-- Initial state
INSERT INTO user_activity VALUES (4324182021466249494, 5, 146, 1)

-- Cancel old row and insert new state
INSERT INTO user_activity VALUES (4324182021466249494, 5, 146, -1)
INSERT INTO user_activity VALUES (4324182021466249494, 6, 185, 1)

-- Query with proper aggregation
SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration
FROM user_activity
GROUP BY UserID
HAVING sum(Sign) > 0
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │         6 │      185 │
└─────────────────────┴───────────┴──────────┘
على عكس ReplacingMergeTree، يتيح لك CollapsingMergeTree تعديل قيم مفتاح الترتيب. وهو مناسب جدًا للعمليات القابلة للعكس التي تتضمن دلالات الإلغاء، مثل المعاملات المالية أو تتبّع حالة اللعبة.
يتطلّب نهج التحديث المذكور أعلاه أن يحتفظ تطبيقك بالحالة على جهة العميل لكي يتمكّن من إدراج صف الإلغاء. ورغم أن هذا هو النهج الأكثر كفاءة من منظور ClickHouse، فقد يكون العمل به على نطاق واسع معقّدًا. كما تحتاج الاستعلامات أيضًا إلى تجميع مع ضرب الإشارة لإنتاج نتائج صحيحة.
اقرأ المزيد: CollapsingMergeTree

التحديثات التصريحية

تعمل هذه الطرق مع الجداول التي تستخدم محركات عائلة MergeTree.
الطريقةالصياغةالأنسب لـالمفاضلات
الطفراتALTER TABLE ... UPDATEالتحديثات المجمّعة غير المتكررة، وتناسب بشكل خاص الحالات التي يتوافق فيها التحديث مع التقسيم في جدولك.عمليات I/O كثيفة؛ يعيد كتابة الأعمدة
التحديثات الخفيفةUPDATE ... SET ... WHEREالتحديثات الصغيرة (~0.1-10% من الصفوف)؛ والتحديثات المتكررة التي تتطلب أداءً جيدًايضيف عبئًا إضافيًا على استعلامات SELECT؛ وتُحتسب أجزاء التصحيح ضمن الحدود

عمليات التعديل

تؤدي عمليات التعديل (ALTER TABLE ... UPDATE) إلى إعادة كتابة جميع الأجزاء التي تحتوي على صفوف تطابق تعبير WHERE.
ALTER TABLE posts UPDATE AnswerCount = AnswerCount + 1 WHERE AnswerCount = 0
تُعدّ التعديلات كثيفة الاستهلاك لعمليات الإدخال/الإخراج، إذ تعيد كتابة جميع الأجزاء التي تطابق تعبير WHERE. ولا تتسم هذه العملية بالذرية. وتُستبدل الأجزاء بالأجزاء التي طُبِّق عليها التعديل بمجرد أن تصبح جاهزة، وسيشاهد استعلام SELECT الذي يبدأ تنفيذه أثناء عملية التعديل بيانات من أجزاء طُبِّق عليها التعديل بالفعل، إلى جانب بيانات من أجزاء لم يُطبَّق عليها التعديل بعد. يمكنك تتبّع حالة التقدّم عبر جدول system.mutations.
التعديلات كثيفة الاستهلاك لعمليات الإدخال/الإخراج، ويجب استخدامها باعتدال لأنها قد تؤثر في أداء SELECT على مستوى العنقود. وإذا تراكمت التعديلات في الطابور بوتيرة أسرع من معالجتها، فسيتدهور أداء الاستعلامات. راقب الطابور عبر system.mutations.
اقرأ المزيد: ALTER TABLE UPDATE

التعديلات الفورية

عند إجراء تعديلات عبر ALTER TABLE ... UPDATE، قد تحتاج إلى الانتظار حتى تُطبَّق هذه التعديلات من خلال عملية في الخلفية لكي ترى القيم المعدَّلة منعكسة في استعلاماتك. يوفّر ClickHouse طريقة لتغيير هذا السلوك من خلال “التعديلات الفورية”. عندما تكون التعديلات الفورية مُمكَّنة، تُعلَّم الصفوف المحدَّثة فورًا، وتُرجِع استعلامات SELECT اللاحقة القيم المعدَّلة تلقائيًا. يمكن تمكين التعديلات الفورية للجداول من عائلة MergeTree عبر تفعيل الإعداد على مستوى الاستعلام apply_mutations_on_fly.
SET apply_mutations_on_fly = 1;
لنُنشئ جدولًا وننفّذ بعض عمليات mutation:
CREATE TABLE test_on_fly_mutations (id UInt64, v String)
ENGINE = MergeTree ORDER BY id;

-- تعطيل materialization في الخلفية لعمليات mutation لعرض
-- السلوك الافتراضي عندما لا تكون on-the-fly mutations مُمكّنة
SYSTEM STOP MERGES test_on_fly_mutations;
SET mutations_sync = 0;

-- إدراج بعض الصفوف في جدولنا الجديد
INSERT INTO test_on_fly_mutations VALUES (1, 'a'), (2, 'b'), (3, 'c');

-- تحديث قيم الصفوف
ALTER TABLE test_on_fly_mutations UPDATE v = 'd' WHERE id = 1;
ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'd';
ALTER TABLE test_on_fly_mutations UPDATE v = 'e' WHERE id = 2;
ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'e';
لنتحقق من نتيجة التحديثات عبر استعلام SELECT:
-- تعطيل on-the-fly mutations صراحةً
SET apply_mutations_on_fly = 0;

SELECT id, v FROM test_on_fly_mutations ORDER BY id;
لاحظ أن قيم الصفوف لم تُحدَّث بعد عند الاستعلام عن الجدول الجديد:
┌─id─┬─v─┐
│  1 │ a │
│  2 │ b │
│  3 │ c │
└────┴───┘
لنرَ الآن ما يحدث عند تمكين on-the-fly mutations:
-- تمكين on-the-fly mutations
SET apply_mutations_on_fly = 1;

SELECT id, v FROM test_on_fly_mutations ORDER BY id;
يُرجع استعلام SELECT الآن النتيجة الصحيحة فورًا، من دون الحاجة إلى انتظار تطبيق عمليات mutation:
┌─id─┬─v─┐
│  3 │ c │
└────┴───┘
تأثير الأداء
عند تمكين التعديلات الفورية، لا تُجسَّد التعديلات فورًا، بل لا تُطبَّق إلا أثناء استعلامات SELECT. ومع ذلك، يُرجى ملاحظة أن التعديلات لا تزال تُجسَّد بشكل غير متزامن في الخلفية، وهي عملية مكلفة. إذا ظل عدد التعديلات المُرسلة يتجاوز باستمرار عدد التعديلات التي تُعالَج في الخلفية خلال فترة زمنية معينة، فسيستمر طابور التعديلات غير المُجسَّدة المطلوب تطبيقها في النمو. وسيؤدي ذلك في النهاية إلى تراجع أداء استعلامات SELECT. نقترح تمكين الإعداد apply_mutations_on_fly مع إعدادات أخرى على مستوى MergeTree مثل number_of_mutations_to_throw و number_of_mutations_to_delay للحد من النمو غير المحدود للتعديلات غير المُجسَّدة.
دعم الاستعلامات الفرعية والدوال غير الحتمية
توفّر On-the-fly mutations دعمًا محدودًا للاستعلامات الفرعية والدوال غير الحتمية. ولا يُدعَم إلا الاستعلامات الفرعية التي تُرجِع قيمة واحدة إذا كان حجم النتيجة معقولًا (ويتحكم في ذلك الإعداد mutations_max_literal_size_to_replace). كما لا تُدعَم إلا الدوال غير الحتمية الثابتة (مثل الدالة now()). تخضع هذه السلوكيات للإعدادات التالية:
الإعدادالوصفالافتراضي
mutations_execute_nondeterministic_on_initiatorإذا كانت القيمة true، فستُنفَّذ الدوال غير الحتمية على الـ replica المُبادِرة، وتُستبدل بقيم حرفية في استعلامات UPDATE وDELETE.false
mutations_execute_subqueries_on_initiatorإذا كانت القيمة true، فستُنفَّذ الاستعلامات الفرعية التي تُرجِع قيمة واحدة على الـ replica المُبادِرة، وتُستبدل بقيم حرفية في استعلامات UPDATE وDELETE.false
mutations_max_literal_size_to_replaceالحد الأقصى لحجم القيم الحرفية المُسلسلة، بالبايت، التي يمكن استبدالها في استعلامات UPDATE وDELETE.16384 (16 KiB)

التحديثات خفيفة الوزن

تستخدم التحديثات خفيفة الوزن “patch parts”—وهي أجزاء بيانات خاصة لا تتضمن سوى الأعمدة والصفوف المُحدَّثة—بدلاً من إعادة كتابة الأعمدة بالكامل كما يحدث في عمليات mutation التقليدية.
UPDATE posts SET AnswerCount = AnswerCount + 1 WHERE Id = 404346
يستخدم هذا النهج صياغة UPDATE القياسية، وينشئ الأجزاء التصحيحية فورًا من دون انتظار عمليات الدمج. تصبح القيم المحدَّثة مرئية فورًا في استعلامات SELECT عبر تطبيق التصحيحات، لكنها لا تُطبَّق ماديًا إلا أثناء عمليات الدمج اللاحقة. وهذا يجعل التحديثات خفيفة الوزن مثالية لتحديث نسبة صغيرة من الصفوف (حتى نحو 10% من الجدول) بزمن استجابة متوقَّع. وتُظهر اختبارات الأداء أنها قد تكون أسرع بما يصل إلى 23 مرة من الطفرات. لكن المقابل هو أن استعلامات SELECT تتحمّل كلفة إضافية عند تطبيق التصحيحات، كما تُحتسب الأجزاء التصحيحية ضمن حدود الأجزاء. وبعد تجاوز عتبة ~10%، تزداد الكلفة الإضافية الناتجة عن تطبيق التصحيحات عند القراءة بشكل متناسب، مما يجعل الطفرات المتزامنة أكثر كفاءة للتحديثات الأكبر. للمزيد: UPDATE خفيف الوزن

الطفرات الفورية

توفر الطفرات الفورية آليةً لتحديث الصفوف بحيث تُرجع استعلامات SELECT اللاحقة تلقائيًا القيم المعدّلة من دون انتظار المعالجة في الخلفية. ويعالج هذا عمليًا قصور الذرية في الطفرات العادية.
SET apply_mutations_on_fly = 1;

SELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐
│     26762 │
└───────────┘
-- Increment the count
ALTER TABLE posts UPDATE ViewCount = ViewCount + 1 WHERE Id = 404346

-- The updated value is immediately visible
SELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐
│     26763 │
└───────────┘
يتطلب كلٌّ من عملية mutation واستعلامات SELECT اللاحقة أن يكون الإعداد apply_mutations_on_fly = 1 مفعّلًا. تُخزَّن شروط mutation في ClickHouse Keeper، الذي يحتفظ بكل شيء في الذاكرة، وتُطبَّق آنيًا أثناء الاستعلامات. لاحظ أن mutation لا تزال تُستخدم لتحديث البيانات، لكنها فقط لا تُطبَّق ماديًا على الفور. وستظل mutation تُنفَّذ في الخلفية كعملية غير متزامنة، وتتحمل نفس الـ overhead المرتفع مثل mutation العادية. كما أن التعبيرات التي يمكن استخدامها مع هذه العملية محدودة أيضًا (راجع التفاصيل).
يجب استخدام on-the-fly mutations فقط لعدد صغير من العمليات—ربما بضع عشرات كحد أقصى. ويخزن Keeper الشروط في الذاكرة، لذا فإن الاستخدام المفرط يؤثر في استقرار العنقود. وقد يتسبب الحمل الكبير على Keeper في مهلات للجلسات تؤثر في جداول غير ذات صلة.
اقرأ المزيد: On-the-fly mutations

ملخص المقارنة

يلخّص الجدول التالي العبء الإضافي على أداء الاستعلامات استنادًا إلى اختبارات الأداء. وتُستخدم Mutations كخط أساس، لأن الاستعلامات تعمل بأقصى سرعة بمجرد اكتمالها وإعادة كتابة البيانات فعليًا.
الطريقةتباطؤ الاستعلامالعبء الإضافي على الذاكرةملاحظات
Mutationsخط الأساسخط الأساسسرعة كاملة بعد الاكتمال؛ تُعاد كتابة البيانات فعليًا
On-the-fly mutationsمتغيرمتغيرظهور فوري؛ يتراجع الأداء إذا تراكم عدد كبير من التحديثات
Lightweight updates7–18% (متوسط ~12%)+20–210%الأكثر كفاءة للاستعلامات؛ الأنسب لتحديث ≤10% من الجدول
ReplacingMergeTree + FINAL21–550% (متوسط ~280%)20–200× من خط الأساسيجب قراءة جميع إصدارات الصفوف؛ وهو الأعلى من حيث العبء الإضافي على الاستعلام
CoalescingMergeTree + FINALمماثل لـ ReplacingMergeTreeمماثل لـ ReplacingMergeTreeيضيف الدمج على مستوى الأعمدة عبئًا إضافيًا مشابهًا
CollapsingMergeTreeيعتمد على التجميعيعتمد على التجميعيعتمد العبء الإضافي على تعقيد الاستعلام

موارد إضافية

إذا كنت مهتمًا بالتعمق في كيفية تطور التحديثات في ClickHouse مع مرور الوقت، إلى جانب تحليل لاختبارات الأداء، فراجع ما يلي:
آخر تعديل في ٢٩ يونيو ٢٠٢٦