پرش به مطلب اصلی

Isolation Level

این مشکلات در دیتابیس وقتی اتفاق می‌افتند که تراکنش‌های همزمان روی داده‌های یکسان تأثیر بگذارند و ایزوله‌سازی به اندازه کافی قوی نباشد.


🛑 اDirty Read (خواندن کثیف)

🔹 زمانی اتفاق می‌افتد که یک تراکنش داده‌ای را بخواند که هنوز در یک تراکنش دیگر Commit نشده است. اگر تراکنش دوم Rollback شود، داده‌ای که تراکنش اول خوانده بود دیگر معتبر نخواهد بود.

مثال:

  1. تراکنش T1 مقدار balance را از 100 به 200 تغییر می‌دهد ولی هنوز COMMIT نکرده است.
  2. تراکنش T2 مقدار balance را به‌عنوان 200 می‌خواند.
  3. تراکنش T1 عملیات را ROLLBACK می‌کند و مقدار balance به 100 برمی‌گردد.
  4. حالا تراکنش T2 مقداری خوانده که هیچ‌وقت تأیید نشده بود!

📌 راه‌حل: سطح ایزوله‌سازی Read Committed یا بالاتر این مشکل را حل می‌کند.


🔁 اNon-repeatable Read (خواندن غیرقابل تکرار)

🔹 زمانی رخ می‌دهد که یک تراکنش دو بار یک داده را بخواند، ولی بین این دو خواندن، تراکنش دیگری مقدار را تغییر دهد. این باعث می‌شود که مقدار خوانده‌شده در هر بار متفاوت باشد.

مثال:

  1. تراکنش T1 مقدار balance را می‌خواند (مثلاً 100).
  2. تراکنش T2 مقدار balance را به 200 تغییر می‌دهد و COMMIT می‌کند.
  3. دوباره تراکنش T1 مقدار balance را می‌خواند و این بار 200 است! 📌 راه‌حل: تنها سطح ایزوله‌سازی Serializable می‌تواند از Phantom Read جلوگیری کند. معمولا کوئری دوما در قالب یک aggergate function هستش
begin tx1,tx2
select PID from sales
// tx2 finished
select sum (qnt) from sales

👻ا Phantom Read

🔹 وقتی یک تراکنش چندین بار یک مجموعه‌ای از داده‌ها را بخواند، اما بین دو خواندن، تراکنش دیگری سطری را اضافه یا حذف کند. در نتیجه، تراکنش اول نتایج مختلفی دریافت می‌کند.

مثال:

  1. تراکنش T1 همه کاربرانی که age > 18 دارند را می‌خواند (مثلاً 5 نفر).
  2. هم‌زمان تراکنش T2 یک کاربر جدید با age = 25 اضافه می‌کند و COMMIT می‌کند.
  3. تراکنش T1 دوباره همان کوئری را اجرا می‌کند، اما این بار 6 کاربر برمی‌گردد!

📌 راه‌حل: تنها سطح ایزوله‌سازی Serializable می‌تواند از Phantom Read جلوگیری کند. البته توی پست گرس همون سطح repeatable read هم جلوش رو میتونه بگیره چون در ابتدای tx یه اسنپ شات میگیره

😥 ا Lost Update

🔹ا Lost Update زمانی رخ می‌دهد که دو تراکنش تقریباً هم‌زمان مقدار یک داده را بخوانند، مقدار جدیدی محاسبه کنند، و آن را بنویسند. اما مقدار نوشته شده توسط تراکنش اول، بدون توجه به تغییراتش، توسط تراکنش دوم بازنویسی می‌شود.


مثال:

  1. تراکنش T1 مقدار balance یک حساب بانکی را می‌خواند (balance = 1000).
  2. تراکنش T2 هم‌زمان مقدار balance را می‌خواند (balance = 1000). 3.ا T1 مبلغ 200 را اضافه می‌کند و مقدار جدید (balance = 1200) را ذخیره می‌کند.
  3. ا**T2** مبلغ 300 را اضافه می‌کند، اما هنوز مقدار قدیمی (1000) را دارد، پس مقدار جدید را 1300 محاسبه کرده و ذخیره می‌کند.
  4. در نتیجه، تغییر T1 (یعنی افزایش 200) از بین می‌رود و مقدار نهایی 1300 خواهد بود، در حالی که باید 1500 می‌شد! 😨

🔹 انواع سطوح ایزوله‌سازی:

1️⃣ اRead Uncommitted (خواندن تغییرات تأییدنشده)

  • پایین‌ترین سطح ایزوله‌سازی
  • یک تراکنش می‌تواند داده‌های تراکنش‌های تأییدنشده (Uncommitted) دیگر را بخواند
  • احتمال بروز Dirty Read، Non-repeatable Read و Phantom Read بالا است
  • سریع اما ناامن

2️⃣ اRead Committed (خواندن تغییرات تأییدشده)

  • تراکنش فقط داده‌های تأییدشده را می‌خواند
  • از Dirty Read جلوگیری می‌کند اما Non-repeatable Read و Phantom Read ممکن است اتفاق بیفتند
  • در اکثر دیتابیس‌ها (مثل PostgreSQL و SQL Server) به‌عنوان سطح پیش‌فرض استفاده میشه

3️⃣ اRepeatable Read

  • یک تراکنش تا پایان اجرا، داده‌هایی که خوانده را قفل می‌کند (تغییرات توسط دیگر تراکنش‌ها مجاز نیست)
  • از Dirty Read و Non-repeatable Read جلوگیری می‌کند اما Phantom Read ممکن است رخ دهد
  • در MySQL (با InnoDB) این سطح پیش‌فرض است
  • توی پست گرس چون توی این سطح ایزوله سازی اول tx یه snapshot از db گرفته میشه بخاطر همین فانتوم رید هم با همین سطح قابل حله

4️⃣ اSerializable

  • بالاترین سطح ایزوله‌سازی
  • تمام تراکنش‌ها به‌صورت سریالی اجرا می‌شوند (یا از طریق قفل‌گذاری یا کنترل نسخه)
  • از Dirty Read، Non-repeatable Read و Phantom Read جلوگیری می‌کند
  • امنیت بالا اما کارایی کمتر (Concurrency کاهش پیدا می‌کند)

در PostgreSQL و MySQL می‌توان سطح ایزوله‌سازی را با دستور زیر تنظیم کرد:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

یا

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;