Database Engineering

تعمق في MVCC: حل مشكلة القراءات الوهمية

يُعد التحكم في التزامن متعدد الإصدارات (MVCC) العمود الفقري لقواعد البيانات Transactional الحديثة. بينما يتفوق في منع قراءات من حجب كاتبات والعكس، فإنه يُدخل شذوذات التزامن الدقيقة مثل القراءات الوهمية وانحراف الكتابة. بالنسبة للمهندسين الذين يبنيون تطبيقات عالية التزامن، يعد فهم الفروق الدقيقة بين مستويات العزل القياسية لـ SQL والتنفيذات الموزعة أمرًا بالغ الأهمية. تفصل هذه المقالة هذه الظواهر في PostgreSQL وCockroachDB.

فهم الشذوذات

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

PostgreSQL: حل قفل الفجوة

تُنفذ PostgreSQL عزلة لقطة قابلة للتسلسل (SSI). بشكل افتراضي، تعمل في وضع Read Committed، ولكن من أجل القابلية للتسلسل، تقدم نهجًا قويًا. على عكس MySQL، لا تستخدم PostgreSQL قفل الفجوة (قفل على نطاقات الفهرس). بدلاً من ذلك، تكتشف تعارضات القراءة-الكتابة. إذا قرأت معاملتان نفس المفتاح وقامت إحداهما بالكتابة على مفتاح مختلف في نفس المجموعة، ترفع PostgreSQL فشلًا في التسلسل. هذا يجبر التطبيق على إعادة محاولة المعاملة. بينما هذا ليس منعًا حقيقيًا قائمًا على "القفل"، فإنه يضمن القابلية للتسلسل مقابل تكلفة إعادة المحاولات أحيانًا.

-- مثال: اكتشاف فشل التسلسل في PostgreSQL
BEGIN;
SELECT count(*) FROM orders WHERE status = 'pending';
-- تقوم المعاملة A بتحديث صف
-- تقوم المعاملة B أيضًا بالقراءة والتحديث
-- قد ترفع PostgreSQL: serialization_failure
COMMIT;
لتخفيف القراءات الوهمية دون استخدام التسلسل الكامل، غالبًا ما يستخدم المطورون SELECT ... FOR UPDATE الذي يقفل الصفوف المحددة المعادة. ومع ذلك، هذا لا يقفل الصفوف المستقبلية التي تطابق الشرط، مما يترك القراءات الوهمية ممكنة ما لم يتم دمجها مع منطق على مستوى التطبيق أو مستويات عزل أعلى.

CockroachDB: التسلسل الموزع

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

-- مثال قابل للتسلسل في CockroachDB
BEGIN;
-- قراءة الرصيد
SELECT balance FROM accounts WHERE id = 1;
-- الكتابة إلى الحساب 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
COMMIT;
-- إذا عدل معاملة أخرى الحساب 1 بشكل متزامن،
-- ستلغي CockroachDB هذه المعاملة بخطأ قابل لإعادة المحاولة.

استراتيجيات عملية

1. إعادة محاولة التطبيق: لكلا القاعدتين، يعد تغليف المعاملات القابلة للتسلسل في حلقة إعادة محاولة مع تأخير أسي أمرًا ضروريًا. 2. القفل المتفائل: استخدم أعمدة الإصدار للكشف عن التعارضات مبكرًا. 3. إزالة التطبيع: في بعض الأحيان، يمكن أن يؤدي نقل القيود إلى طبقة التطبيق أو استخدام المشاهد المادية إلى تقليل احتقان القفل.

الخاتمة

يتطلب حل القراءات الوهمية وانحراف الكتابة تحولًا في العقلية. لم تعد تكتب SQL فحسب؛ بل تصمم من أجل التزامن. تقدم PostgreSQL مرونة مع SSI وإعادة المحاولات، بينما توفر CockroachDB توافقًا قويًا مع اكتشاف التعارضات التلقائي. اختر مستوى العزل الذي يوازن بين الأداء والدقة الصارمة التي يتطلبها منطق عملك.
Share: