Yazan Qwaider

تعرّف على نموذج ACID في قواعد البيانات

6 دقيقة - وقت القراءة
تاريخ النشر : قبل ٥ أشهر

إذا كنت قد سمعت عن ACID من قبل وقرأتها اسيد وأدركت أنها (حمض), فمعذرةً يا صديقي الكيميائي 😁 فأنت بحاجة إلى تكملة قراءة الكلام التالي لأنه قد يغيّر أفكارك عن ذلك الحمض قليلاً, وإذا كنت قد عرفتها فمن الممكن أن يكون هناك معلومة جديدة قد تفيدك معرفتها, لأننا حاولنا أن نغطّي أغلب الأمور عن هذا النموذج.


في البداية ال ACID هو نموذج (model) في قواعد البيانات يحتوي على عدة خصائص, وكلمة ACID هي من اشتقاق أول الحروف من تلك الخصائص, والتي هي:

A  =>  Atomicity

C  =>  Consistency

I   =>  Isolation

D  =>  Durability

والفكرة الرئيسية من ورائها هي بناء قواعد بيانات بأداء عالي و بيانات متناسقة وصحيحة.


ستقول, كيف ذلك ؟؟

عندما تبني مشروع معين, ويكون يتعامل مع database بطريقة تسمح بوجود بيانات غير صحيحة في قاعدة البيانات, فهذا قد يسبب الكثير من المشاكل على صعيد القراءات, فمثلاً قد يقرأ المستخدم أن برصيده 100$ وليس هناك funds في حسابه,,, وعلى صعيد ال errors فقد تحدث هذه المشاكل بسبب تلك البيانات الخاطئة Runtime Exception, وذلك ان ال logic لل business  يتوقع من البيانات أن تكون بشكل محدد, ومن غير الطبيعي أن تكون بغير هذا الشكل, ولكن بسبب عدم الاهتمام ب ACID قد تحدث هذه المشاكل والتي يمكنك تجنبها قدر الإمكان. هل ترى ذلك, قد بدأ الأمر يبدو ممتعاً, أليس كذلك 👉.


قبل أن نتحدث عن كل خاصية من الخصائص, سنتعرف على مصطلح ال Transaction, ولنرى كيف سيخدمنا في تلك الخصائص.

ال Transaction هو مجموعة من العمليات التي يجب أن تتم مع بعضها البعض, وكأنها سلسلة, ومن الممكن أن تنظر إليها وكأنها حبل فيه عدّة عُقد وكل عقدة تمثل statement معينة مثل  INSERT, UPDATE, إذا تم قطع الحبل من أي مكان لن يكون له أية فائدة بعدها.


وطريقة كتابتها في MySQL يكون كالتالي:

علماً أننا لن نخوض في ال code في هذا المقال, لأنه الهدف منه شرح المفاهيم.

plain
START TRANSACTION;

statements

COMMIT;

وفي موضع ال statements تكتب الأوامر والعمليات التي تريد تنفيذها على ال database.

* * *

والآن لنرى ما هي خصائص ال ACID وما هي طبيعة ال transaction في كلٍّ منها:

Atomicity

لو أردنا ترجمتها حرفياً لكانت "الذرية" (وهي جزء لا يتجزأ) ولو وضعنا ذلك في السياق, لكان معناها نفس معنى ال Transaction الذي تحدثنا عنه, أي هي مجموعة من العمليات التي يجب أن تتم كلها بنجاح, وإذا فشلت إحداها فإن الجميع سيفشل وسيتم التراجع عن جميع العمليات التي تم عملها في هذه المجموعة قبل الفشل.

وممكن أن نلخّص ذلك ب All or nothing.


Consistency

وتعني أنه يجب أن تكون البيانات المخزنة في قاعدة البيانات منطقية ومترابطة مع بعضها بشكل صحيح.

مثال على ذلك:

لنفترض أن هناك حساب مستخدم User في جدول users ومن المفترض أن لكل user عمود اسمه balance ويبدأ بصفر, ولكن قد يزيد أو ينقص على حسب السحوبات التي تمت أو التمويلات, funds or withdrawals, فلو وجدنا أن قيمة balance ل user معين هي 100$ ولم يكن هناك إلّا سحب واحد بقيمة 20$ وتمويل بقيمة 100$, فإن هناك عدم تناسق inconsistency لأنه من المفترض وجود تمويل آخر بقيمة 20$ أو عدم وجود السحب أساساً ليصبح هناك تناسق ما بين السحوبات والتمويلات مع الرصيد.

وهذه المشكلة قد تنتج في حال عدم اتباعنا لمبادئ أو خصائص الـ ACID. نعم الأمر خطير كما توقعت.


Isolation

العزل, وهو العزل بين ال transactions, وهذا يعني وضع حدود في القراءة للبيانات في كل transaction في حال كان هناك أكثر من transaction تعمل بنفس الوقت, أي (concurrent transactions), بحيث لا يصبح هناك اختلاط بالقراءات تنتج بيانات خاطئة بالتبعية, وهذا يجعل ال transaction تتصرف وكأن ال database خاصة بها فقط (وطبعاً على حسب المستوى المستخدم -سنتحدث عن ذلك أيضاً, لا تقلق-).


هناك أربع مستويات لل isolation أو العزل, وسنبدأ بالأضعف إلى الأقوى (والضعف والقوة هنا ليسا مقياس للأفضلية إنما مقياس لدرجة العزل لأنه قد يكون في بعض الحالات أن المستوى الأضعف هو المستوى المناسب للاستخدام وإلّا لما وجد) :


    1. Read Uncommitted

وهي أضعف المستويات, حيث أنها لا تضع أي locks على ال reads التي تتم أثناء عمل ال transactions, أي أنه لو كان هناك Transaction A و Transaction B وقامت B بالتعديل على column معين, ولم تعمل commit بعد, ومن ثم قامت A بالقراءة ستقوم بالحصول على قيمة ال column بقيمته الجديدة من B:


Image

ولانها تسمح بالقراءة بدون أي قيود, فإن هذا سيسبب Dirty Read, لنفرض أن ال Transaction B فشلت, هذا يعني أنها سيتم عمل لها rollback أي التراجع عن التعديل, ولكن ماذا عن القيمة الحديثة التي حصلت عليها A, ستبقى عندها بيانات خاطئة وهذا هو ال Dirty Read.

ومن الأمثلة عليها هي تطبيقات real-time analytics, حيث أن السرعة العالية فيها قد تكون أهم من الدقة العالية فيها.

وهناك setting اسمه NOLOCK إذا تم تطبيقه على كل ال tables لكل SELECT statements, سيكون بمثابة هذا المستوى أي سيسمح بال Dirty Read.


    2. Read Committed

في هذا المستوى سيتم حل مشكلة Dirty Read التي قد تحدث في المستوى الأول, وذلك من خلال عدم السماح بالقراءة إلّا لل committed transactions فقط, ولكن هل هو الأنسب وننتهي على هذا ؟! لا يا عزيزي, انتظر لكل إيجابية لا بد من سلبية هناك تنظر إلينا (Trade-off), والمشكلة هي انه بسبب أن كل SELECT ستتم أخذ snapshot للبيانات قبل تنفيذها, وبالتالي ستلاحظ ال SELECT التغييرات التي تحدث, وهذا ما يسمى بال Non-Repeatable READ.


Image

وأقرب مثال عليها, هي تطبيقات التسوق, قد يحدث أن يكون موجود كمية 10 حبات من سلعة معينة, وتقوم بالطلب فلا تجد منها كمية, وهذا بسبب أن بنفس الوقت قام أحدهم بعمل order أو قبل بوقت لا يتعدى الثانية مثلاً لحسن حظه وانتهى قبلك, ومن ثم عند قراءة ال transaction الخاص بك للكمية وجدها أقل من الذي رأيته من قبل, لأنه قد شاهد ال committed changes.

وهذا المستوى هو ال default level في أغلب قواعد البيانات ما عدا MySql.


    3. Repeatable Read

في هذا المستوى يتم حل مشكلة الـ dirty read وال non-repeatable read, بحيث أن أول SELECT يتم أخذ snapshot قبل تنفيذها, ومن ثم كل ال SELECT التي بعدها ستبقى تتعامل مع ال snapshot الأولى ولا يتم عمل reset لل snapshot كما في Read Committed, وهذا حتى لو كان هناك transactions أخرى قامت بالتعديل على نفس البيانات وعملت لها commit أثناء عمل ال transaction الحالية, فإن هذا لن يغير شيئ, ستبقى ال data تكرر كما في snapshot.

ومن الأمثلة عليه, في الحالات التي قد تحتاج فيها أن تقرأ أكثر من مرة وتحصل على نفس النتيجة في نفس ال transaction.

وهذا هو ال default في MySql, ومع أنه يحافظ على أن تكون البيانات consistent إلّا أنه يجعل performance أسوأ لأنه سوف يبقى يعمل maintenance على snapshot.


    4. Serializable

هذا المستوى هو أقوى مستوى من المستويات في العزل بين ال transactions, وهو شبيه بال Repeatable إلّا أنه يضع restrictions أكثر على مستوى row, حيث أنه لن يسمح لأي transaction بالتعديل على أي row هو يعمل عليه حتى ينتهي, أي أنه يقوم بتحويل كل SELECT Statements إلى SELECT … FOR SHARE أي أنه يجعل ال transactions الأخرى تقرأ فقط ال rows بدون إمكانية التعديل أو الحذف.

ومن الأمثلة عليها في الأنظمة التي يكون فيه دقة البيانات أهم من performance مثل الأنظمة المالية والبنوك.

 AWS AURORA ب MySQL and PostgreSQL engine تدعم هذا المستوى.


من ضمن المشاكل التي قد تحدث في بعض المستويات مشكلة Phantom Read, ودعونا نترجمها بلا تحفّظ إلى (القراءة الشبح 👻👻), وتحدث عندما يكون Transaction A قد عمل lock على indexed rows وليس على gap معين, ولنفرض أن هناك ids: 99, 100, ومن ثم قامت Transaction B بعمل insert على نفس ال table, ومن بعدها قامت Transaction A بالقراء مرة أخرى ستجد أن هناك row جديد (وهو الشبح).



وجدت هذه الصورة على الإنترنت تلخص المشاكل في كل من المستويات الأربعة بشكل منظم:

Image


Durability

وهي ضمان أن ال committed transactions يجب أن تكون محفوظة في database بغض النظر إذا حصل failure في database بانقطاع الكهرباء وتوقف ال server عن العمل أو لآخره من المشاكل التي قد تحدث, فإنها سوف تبقى مخزّنة لأنها أصبحت في disk.



وفي النهاية هناك تصنيفين لل Transaction Schemas:

   1. optimistic

المتفائل, وهو يتوقع أن ال transactions الأخرى لن تقرأ أو تعدل على نفس البيانات

التي يعمل عليها transaction الحالي.

وفي حال حدث أكثر من transaction يعمل على نفس البيانات, سيتم الغاء الجميع والمحاولة مرة أخرى.


   2. pessimistic 

المتشائم, وهو كالمنظوي على نفسه مع فارق التشبيه, حيث أن ال transaction تجعل 

ال resources موقوفة عليها, فلا أحد يستخدمها غيرها, ومن يريد استخدامها سيضطر إلى

الإنتظار حتى انتهائها ال transaction الحالي منها.



وفي الخاتمة, هناك مشهد في فيلم كدة رضا يبيّن لك حجم الكارثة التي قد تحدث في حال إهمال ال ACID وتحديداً ال Isolation Levels:

وخد بالك من جملة:

 الثانية هتفرق, احنا لازم نصرف الفلوس بنفس التوقيت, عشان الكمبيوتر ميلحقش يسجّل الصح 😂

أحمد حلمي


* * *

لمعرفة المزيد يمكنك الإطلاع على بعض المصادر: