در قسمت قبلی راجع به درجهی انزوا و انزوای ایده آل صحبت کردیم. حالا میخوایم در مورد مشکلات همزمانی ای که ممکنه با استفاده از این درجههای پایین تر انزوا دچار اونها بشیم صحبت کنیم و در نهایت هم متوجه بشیم برای کاری که میخوایم انجام بدیم درجهی انزوای مناسب رو چطوری انتخاب کنیم.
استاندارد SQL درجههای انزوای متعددی پایین تر از درجهی serializability معرفی میکنه. علاوه بر این تعدادی درجهی انزوا هم به صورت معمول در دیتابیسهای موجود در سیستم های تجاری استفاده میشه که توی استاندارد SQL تعریف نشده (یکی از معروف ترین مثال های این موارد snapshot isolation است که توی استاندارد SQL وجود نداره). اما قبل از بررسی کردن درجههای مختلف انزوا بیایید راجع به مشکلات شناخته شدهای که ممکنه توی یه سیستم همزمان با درجهی انزوای پایین تر از serializability پیش بیاد صحبت کنیم. بیایید این مشکلات رو با مثال بررسی کنیم.
بیایید فرض کنیم وقتی یکی از محصولات فروشگاه آنلاین ما به فروش میرسه این تراکنشهای اجرا میشه:
اگر این تراکنشها به صورت سریالی اجرا بشن، همیشه مقدار موجودی اولیه قابل محاسبه است. اگر با ۴۲ محصول شروع کنیم، همیشه تعداد موجودی به علاوهی تعداد سفارشها برابر با ۴۲ خواهد بود. اما اگر تراکنشها به صورت همزمان و با درجهی انزوای کمتر از serializability اجرا بشن چی؟
برای مثال، فرض کنید دو تراکنش به صورت همزمان درحال اجرا هستن و هر دو مقدار موجودی اولیه را مقدار یکسانی (۴۲) میخوانند. بعد هر دو تلاش میکنند که مقدار موجودی جدید که یکی کمتر از موجودی قبلی است (۴۱) را به عنوان موجودی جدید بنویسند و یک سفارش جدید در جدول سفارشها اضافه کنند. در این صورت وضعیت جدید این خواهد بود که مقدار موجودی ۴۱ است با این حال دو سفارش جدید در جدول سفارشها ثبت شده (با این وضعیت تعداد کل محصولات معادل ۴۳ عدد میشه). ما یه محصول اضافه به وجود آوردیم! خب واضحه که این یه خطاست. این خطا به lost-update anomaly معروفه.
یه مثال دیگه، بیایید فرض کنیم باز هم همون دو تراکنش دارند هم زمان اجرا میشن فقط این بار تراکنش دوم وقتی تراکنش اول بین مرحلهی ۲ و ۳ است آغاز میشه. در این حالت، تراکنش دوم مقدار موجودی رو پس از کاهش میخونه. برای مثال اگه مقدار موجودی قبل از شروع تراکنش اول رو ۴۲ بگیریم تراکنش اول موجودی رو میخونه و بعد یک واحد ازش کم میکنه بعد تراکنش دوم موجودی رو میخونه و باز یک واحد کمترش میکنه (یعنی ۴۰ تا موجودی داریم الان) در همین حال تراکنش اول در مرحلهی ایجاد سفارش لغو میشه (مثلا به علت کمبود موجودی). در این حالت تراکنش اول در فرایند لغو تراکنش وضعیت پایگاه داده رو به حالت قبل از شروع تراکنش خودش برمیگردونه (یعنی موجودی ۴۲). پس در حالت نهایی ما ۴۲ تا محصول موجودی داریم و یک عدد سفارش جدید (دومین تراکنش موفقیت آمیزه و سفارش دوم با موفقیت ثبت میشه). تو این حالت باز هم میبینیم که یه محصول اضافه به وجود اومده! این خطا به dirty-write anomaly معروفه (به این علت که به تراکنش دوم اجازه داده میشه که قبل از مشخص شدن وضعیت نهایی سفارش اول مقدار موجودی رو تغییر بده).
برای مثال سوم، بیایید فرض کنیم یک تراکنش برای محاسبهی کل محصولات موجود و فروخته شده میخواد مقدار موجودی و تعداد کل سفارشها رو بخونه. این تراکنش بین مرحلهی ۲ و ۳ از یک تراکنش خرید اجرا میشه در این حالت تراکنشی که قصد داره موجودی کل محصولات و سفارشها رو بخونه با یک حالت غیر قطعی میانی رو به رو میشه که یک واحد از موجودی کم شده اما هنوز سفارشی ثبت نشده. در این حالت اینطور به نظر میرسه که یک محصول گم شده (یک خطای دیگه!). این خطا به dirty-write anomaly معروفه، به این علت که به تراکنشی که در حال محاسبهی کل محصولات بوده اجازه داده میشه که یک حالت میانی غیر قطعی از یک تراکنش خرید رو بخونه.
برای مثال چهارم، بیایید فرض کنیم یک تراکنش مستقل وجود داره که هر بار موجودی رو میخونه و اگر کالا های موجود کمتر از ۱۰ عدد باشن کالا های جدید سفارش میده( با دو شرط زیر):
شرط اول: IF (READ(Inventory) = (10 OR 11 OR 12)) (اگر موجودی ۱۰، ۱۱ یا ۱۲ باشه):
ارسال با پست معمولی
شرط دوم: IF (READ(Inventory) < 10) (اگر موجودی کمتر از ۱۰ باشه):
ارسال با پست پیشتاز
توجه کنید که این تراکنش دوبار موجودی رو میخونه. اگر تراکنش خرید در زمانی که این تراکنش بین مرحلهی اول و دوم شرط هست اجرا بشه تراکنش برای هر شرط مقدار موجودی متفاوتی میخونه. اگر موجودی قبل از تراکنش خرید ۱۰ باشه این باعث میشه که درخواست ارسال دوبار فرستاده بشه یک بار با پست معمولی، یک بار با پست پیشتاز. این خطا به non-repeatable read anomaly معروفه.
برای مثال پنجم، تصور کنید یک تراکنش وجود داره که روی جدول سفارشات اجرا میشه تا بیشینه (maximum) قیمت یک سفارش رو حساب کنه. سپس دوباره اجرا میشه تا میانگین سفارشات رو حساب کنه. فرض کنید بین اجرای این دو مرحله تراکنش یک تراکنش خرید اجرا بشه که قیمت اون انقدر بالا باشه که میانگین محاسبه شده در مرحلهی دوم از بیشینهی محاسبه شده در مرحلهی اول بالا تر بره. این تراکنش میانگینی رو محاسبه میکنه که از بیشینه بیشتره. یک اتفاق نشدنی و یک خطا که هیچ وقت در یک سیستم serializable رخ نمیده. این خطا با خطای non-repeatable read anomaly متفاوته چون تمام مقادیر خوانده شده در هر دو تراکنش یکسانه و دلیل اتفاق افتادن خطا اضافه شدن یک سفارش جدید در بین دو مرحله تراکنشه این خطا phantom read anomaly نامیده میشه.
برای مثال آخر، تصور کنید میخواهیم برنامهای طراحی کنیم که قیمت را بر اساس موجودی تغییر دهد. برای مثال در بسیاری از خطوط هوایی قیمت بلیط با کاهش صندلی موجود در پرواز افزایش مییابد. فرض کنید برنامه از یک فرمول برای چک کردن برقراری ارتباط بین موجودی و قیمت استفاده میکنه ( برای مثال: 10I + P >= $500 که در آن I موجودی و P قیمت است) و این فرمول را به صورت شرط برای database تعریف میکنه. قبل از هر خرید تراکنش خرید این شرط را چک میکنه و اگر این شرط برقرار بود تغییری لازم برای فرایند خرید را اعمال میکنه.
به طور مشابه، یک تراکنش مستقل که تخفیفهای مخصوص اعمال میکنه هم موجودی و قیمت را چک میکند که اگر بعد از اعمال تخفیف همچنان شرط پا برجا باشه تخفیف رو اعمال کنه.
حالا فرض کنید این دو تراکنش هم زمان در حال اجرا باشن (هر دو مقدار قدیمی موجودی و قیمت رو بخونن) و به صورت مستقل با در نظر گرفتن شرط قیمت ها رو بهروز کنن. در این صورت متاسفانه ممکنه مقادیری از قیمت و موجودی تولید بشن که شرط رو نقض میکنن. اگر این تراکنش ها پشت سر هم اجرا میشدن با اجرا شدن تراکنش اول قیمت و موجودی تغییر میکرد و تراکنش دوم بعد از چک کردن شرط و عدم تطابق متوقف میشد. اما چون این فرایند ها به صورت مستقل عمل میکنن هر دو بعد از خواندن مقادیر قدیمی قیمت و موجودی تصمیم به ادامهی فرایند میگیرن. این خطا write skew anomaly نامید میشه.