الحفاظ على سلامة البيانات مع مستويات عزل المعاملات وضوابط التزامن

مكتمل

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

كيف تعمل مستويات العزل

تحدد مستويات العزل مقدار ما يمكن أن تراه معاملة واحدة من العمل غير الملتزم بمعاملة أخرى. يؤطرها معيار SQL حول ثلاثة آثار جانبية متزامنة، كل منها يرتبط بمشكلة حقيقية في تطبيقاتك. لنستخدم تطبيق تجارة إلكترونية لتوضيحها:

  • قراءات قذرة: طلب العميل يقرأ سعرا مخفضا حددته معاملة أخرى لكنه لم يلتزم بعد. إذا تراجعت المعاملة التي حددت السعر، فإن الأمر استخدم سعرا لم يكن موجودا أبدا. تعرف القراءات الفاسدة بأنها قراءة بيانات غير ملتزمة من معاملة أخرى، مما قد يؤدي إلى سلوك غير صحيح إذا لم تنفذ تلك المعاملة الأخرى.
  • قراءات غير قابلة للتكرار: في نفس المعاملة، تقوم خدمة المخزون بقراءة عدد المخزون لعنصر معين. بعد القراءة الأولية، تقوم المعاملة ببعض الحسابات، ثم تقرأ نفس الصف مرة أخرى. بين هذه الخطوات، حدث تغيير في معاملة أخرى، لذا أصبح العد مختلفا الآن. تحدث القراءات غير القابلة للتكرار عندما تقرأ معاملة نفس الصف مرتين وتحصل على قيم مختلفة في كل مرة.
  • يقرأ Phantom: التقرير يستقيل جميع الأوامر التي تم وضعها في الساعة الماضية، ويحسب المجموع، ثم يشغل نفس الاستعلام بعد لحظات ضمن نفس المعاملة. معاملة أخرى أدخلت طلبات جديدة بينهما، لذا الإجماليات لا تتطابق. تحدث قراءات الوهمية عندما تعيد معاملة تنفيذ استعلام وترى صفوفا جديدة لم تكن موجودة من قبل.

تخيل تطبيق التجارة الإلكترونية لدينا خلال بيع سريع مع مئات العملاء المتزامنين. يمكن أن تظهر جميع الآثار الجانبية الثلاثة دفعة واحدة. مستوى العزل الذي تختاره يحدد أي المستويات تتحملها تطبيقك وكم من الحظر يتقبل مقابل ذلك.

لاحظ أن كل من هذه المشاكل تتضمن معاملة تقوم بعدة عمليات: قراءة صف، القيام ببعض العمل، ثم القراءة مرة أخرى أو اتخاذ قرار. عادة ما تكتمل معاملة البيان الواحد بسرعة كبيرة بحيث نادرا ما تظهر هذه التعارضات. مستويات العزل تكون أكثر أهمية عندما تمتد معاملاتك عبر عدة عبارات، وهو أمر شائع في منطق الأعمال الذي يقرأ البيانات، ويطبق القواعد، ثم يكتب النتائج.

إليك نمط نموذجي حيث تلعب مستويات العزل دورها. تخيل عميلين يحاولان شراء آخر وحدة من نفس المنتج:

-- Transaction A (Customer 1)          -- Transaction B (Customer 2)
BEGIN TRANSACTION;
                                        BEGIN TRANSACTION;
SELECT @Stock = StockCount
  FROM Products
  WHERE ProductID = 42;
-- @Stock = 1, proceed with order
                                        SELECT @Stock = StockCount
                                          FROM Products
                                          WHERE ProductID = 42;
                                        -- @Stock = 1, proceed with order

UPDATE Products
  SET StockCount = StockCount - 1
  WHERE ProductID = 42;
INSERT INTO Orders (ProductID, Quantity)
  VALUES (42, 1);
COMMIT TRANSACTION;
                                        UPDATE Products
                                          SET StockCount = StockCount - 1
                                          WHERE ProductID = 42;
                                        INSERT INTO Orders (ProductID, Quantity)
                                          VALUES (42, 1);
                                        COMMIT TRANSACTION;
-- StockCount is now -1. Two orders placed for one item.

كلتا المعاملتين قرأتا عدد الأسهم 1 وقررت كلتاهما المضي قدما. يحدد مستوى العزل ما إذا كانت المعاملة B ترى القيمة الأصلية أو تنتظر حتى تنتهي المعاملة A أولا. إذا سمح مستوى العزل بقراءة غير مبتذلة، فإن المعاملة B تقرأ القيمة غير الملتزمة 1 وتستمر في التقدم، مما يؤدي إلى الإفراط في البيع. إذا كان مستوى العزل أكثر تقييدا، تنتظر المعاملة B حتى تلتزم المعاملة A. إذا تم الالتزام بالمعاملة A بنجاح، ترى المعاملة B عدد الأسهم المحدث 0 ويمكنها منع مرور الترتيب الثاني. إذا تراجعت المعاملة أ لأي سبب، ترى المعاملة ب القيمة الأصلية 1 ويمكنها الاستمرار دون قراءة بيانات غير ملتزمة.

يدعم SQL Server وAzure SQL Database ستة مستويات عزل. تستخدم الأربعة الأولى التزامن المتشائم (القفل). آخر نسختين يستخدمان التزامن المتفائل (إصدار الصف).

مستويات العزل القائمة على الأقفال

مع العزل القائم على الأقفال، يستخدم محرك قاعدة البيانات أقفالا مشتركة وحصرية لمنع المعاملات من التداخل مع بعضها البعض. كلما كان المستوى أكثر تقييدا، زادت مدة بقاء تلك الأقفال. تقل الآثار الجانبية التي تمر بسرعة، لكن المزيد من المعاملات تنتظر.

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

READ COMMIT هو مستوى العزل الافتراضي في SQL Server ويحقق توازنا أساسيا. كل قراءة تحصل على قفل مشترك، تلتقط البيانات، وتحرر القفل فورا. فك القفل بسرعة يكفي لمنع القراءات غير الواضحة. ومع ذلك، لا شيء يمنع معاملة أخرى من تغيير الصف بين قراءتك، لذا يمكن أن تحدث قراءات غير قابلة للتكرار وقراءات وهمية.

القراءة القابلة للتكرار تذهب خطوة أبعد من READ COMDATED. يحتفظ بالأقفال المشتركة على كل صف تقرأه حتى تكتمل معاملتك. الآن لا يمكن لتلك الصفوف أن تتغير أثناء تفعيل المعاملة، مما يمنع القراءات غير القابلة للتكرار أو القراءات غير القابلة للتكرار. لكن المعاملات الأخرى يمكنها إدخال صفوف جديدة تتطابق مع مرشح الاستعلام الخاص بك، لذا تظل القراءات الوهمية ممكنة.

SERIALIZABLE تهتم بكل ما تفعله المستويات السابقة وأكثر. يتطلب الأمر قفالات مدى تغطي ليس فقط الصفوف التي تقرأها بل أيضا الفجوات بين قيم المفاتيح، مما يمنع الإدخالات داخل تلك النطاقات. القراءات السخيفة، والقراءات غير القابلة للتكرار، والقراءات الوهمية كلها ممنوعة. لكن التكلفة حقيقية: القفل في المدى يسبب حجبا أكبر بكثير ويزيد من فرصة حدوث الجمود. احتفظ بهذا المستوى لسيناريوهات مثل التسوية المالية أو حجز المخزون حيث قد تسبب القراءات الوهمية أخطاء حسابية.

مستويات عزل النسخة الصفية

ماذا لو لم يضطر القراء للانتظار للكتاب على الإطلاق؟ هذا السؤال هو ما يعالج عزل الصف. بدلا من الحظر خلف الأقفال، يحتفظ محرك قاعدة البيانات بالإصدارات السابقة من الصفوف في مخزن الإصدارات. عندما تحتاج معاملة إلى قراءة البيانات التي تقوم معاملة أخرى بتعديلها، فإنها تقرأ من مخزن الإصدارات بدلا من الانتظار. في قاعدة بيانات Azure SQL، يتم تفعيل استعادة قاعدة البيانات المعجل (ADR) دائما، لذا يكون مخزن الإصدارات موجودا داخل قاعدة البيانات نفسها باستخدام مخزن الإصدارات الدائم (PVS).

عزل اللقطة الملتزمة بالقراءة يغير سلوك READ COMMIT على مستوى قاعدة البيانات. مع تفعيل عزل لقطة القراءة الملتزم (RCSI)، ترى كل عملية قراءة لقطة للبيانات كما كانت موجودة في بداية تلك الجملة. لا يزال الكتاب يأخذون أقفالا على الصفوف التي يعدلونها، لكن القراء لا يحجبون خلفها أبدا. التفصيل المهم: RCSI مفعل افتراضيا في قاعدة بيانات Azure SQL، لذا تحصل على هذا السلوك من البداية دون أي تغييرات في الكود.

عزل SNAPSHOT يأخذ هذا الحل خطوة أبعد. بدلا من لقطة لكل بيان، يرى كل قراءة البيانات كما كانت موجودة في بداية المعاملة بأكملها. إذا كان تطبيقك يحتاج إلى تشغيل عدة استعلامات داخل معاملة واحدة والحصول على نتائج متسقة عبر جميعها، فإن عزل SNAPSHOT يوفر هذا الضمان. لاستخدامه، يجب تفعيله ALLOW_SNAPSHOT_ISOLATION في قاعدة البيانات وتعيين مستوى العزل بشكل صريح في الجلسة. هناك أمر يجب الانتباه له: إذا حاولت معاملتان تعديل نفس الصف، فإن عزل SNAPSHOT يثير تعارض التحديثات بدلا من السماح لإحداهما بالكتابة فوق الأخرى بشكل غير متقن.

يلخص الجدول التالي سلوك كل مستوى عزلة:

مستوى العزل قراءات قذرة قراءات غير قابلة للتكرار قراءات الشبح الحواجز بين القراء والكتاب
اقرأ غير ملتزم ‏‏نعم‬ ‏‏نعم‬ ‏‏نعم‬ لا
اقرأ ملتزما لا ‏‏نعم‬ ‏‏نعم‬ ‏‏نعم‬
قراءة قابلة للتكرار لا لا ‏‏نعم‬ ‏‏نعم‬
SERIALIZABLE لا لا لا نعم (قفل المدى)
اقرأ صورة ملتزمة لا ‏‏نعم‬ ‏‏نعم‬ لا
لقطة التقرير لا لا لا لا (تعارض التحديثات ممكن)

تقليل الحجب باستخدام القفل المحسن

يلغي RCSI حجب القراءة والكتابة. لكن ماذا عن حجب الكتابة والكتابة؟ القفل التقليدي يحتفظ بقفل الصفوف والصفحة لكل صف معدل حتى يتم تنفيذ المعاملة. في ظل التزامن الكبير للكتابة، تتراكم الأقفال المثبتة بسرعة.

تعالج Azure SQL Database هذه المشكلة من خلال القفل المحسن، الذي يكون مفعلا دائما ويعمل جنبا إلى جنب مع RCSI. يستخدم آليتين:

  • قفل معرف المعاملة (TID): بدلا من الاحتفاظ بأقفال مفتاح أو صفوف فردية لكل صف معدل، يأخذ المحرك قفلا حصريا واحدا على معرف المعاملة (TID). المعاملات الأخرى التي تحتاج للوصول إلى نفس الصف تحصل على قفل مشترك على TID وتنتظر إكمال المعاملة المعدلة. والنتيجة هي عدد أقل بكثير من الأقفال التي تحتفظ بها في الذاكرة، مما يجعل تصعيد الأقفال أقل احتمالا بكثير.
  • القفل بعد التأهيل: قبل تعديل الصف، يقرأ المحرك أحدث نسخة ملتزمة دون الحصول على قفل ويتحقق مما إذا كان الصف يطابق مسند الاستعلام. فقط الصفوف التي تؤهل فعليا يتم قفلها، ويتم إطلاق هذا القفل الحصري بمجرد اكتمال تحديث الصف، وليس في نهاية المعاملة. يتطلب القفل بعد التأهيل (LAQ) تفعيل RCSI.

التأثير العملي كبير. يتم تحرير أقفال الصفحات والصفوف للتعديلات بمجرد تحديث كل صف بدلا من الاحتفاظ بها حتى يتم تنفيذ المعاملة. الكتابات المتزامنة التي تلامس صفوفا مختلفة نادرا ما تحجب بعضها البعض، حتى تحت الحمل الذروة.

اختر مستوى العزل المناسب

مع توفر ستة مستويات للعزل، كيف تقرر؟ ابدأ بالإعدادات الافتراضية. بالنسبة لمعظم أعباء عمل قاعدة بيانات Azure SQL، فإن الجمع بين RCSI والقفل المحسن يوفر بالفعل أفضل توازن بين الاتساق والأداء. القراء لا يعيقون الكتاب. الكتاب لا يعيقون القراء. ذاكرة القفل تبقى منخفضة. لا تحتاج لتغيير أي شيء.

تقدم إلى عزل SNAPSHOT عندما يشغل تطبيقك عدة استعلامات داخل معاملة واحدة ويحتاج إلى نتائج متسقة عبر جميعها. تخيل تقريرا ماليا يستفسر عن أرصدة الحسابات ثم يجمعها: تحتاج إلى كلا الاستعلامات لرؤية نفس بيانات النقطة الزمنية.

احتفظ بإمكانية التفكير للسيناريوهات التي قد تسبب فيها قراءات وهمية أخطاء تجارية حقيقية، مثل حجز المخزون المزدوج أو حساب التسوية بشكل خاطئ. اقبل أن انخفاض معدل النقل والتوقف في التيار يصبح أكثر احتمالا.

مهم

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

النقاط الموجزة الأساسية

تتحكم مستويات عزل المعاملات في المقايضة بين اتساق البيانات وتزامنها. العزل الأعلى يمنع المزيد من الآثار الجانبية لكنه يزيد من الانسداد. تدعم قاعدة بيانات Azure SQL نظام RCSI بشكل افتراضي، مما يلغي حجب القراءة والكتابة باستخدام إصدار الصفوف بدلا من الأقفال المشتركة. يقلل القفل المحسن أكثر من الحجب عن طريق تحرير قفل الصفوف والصفحات بمجرد تحديث كل صف. بالنسبة لمعظم أعباء العمل، يوفر المزيج الافتراضي من RCSI والقفل المحسن أفضل توازن. احتفظ بعملية الترجمة للسيناريوهات التي قد تسبب فيها القراءات الوهمية أخطاء منطقية في الأعمال.