کنترل همزمانی نسخهای (MVCC) ستون فقرات پایگاهدادههای تراکنشی مدرن است. اگرچه در جلوگیری از مسدود شدن خوانندگان توسط نویسندگان و برعکس عالی عمل میکند، اما ناهنجاریهای همزمانی ظریفی مانند خواندن فانتوم و انحراف نوشتن را ایجاد میکند. برای مهندسانی که برنامههای با همزمانی بالا میسازند، درک ظرافتهای بین سطوح جداسازی استاندارد SQL و پیادهسازیهای توزیعشده حیاتی است. این پست این پدیدهها را در PostgreSQL و CockroachDB کالبدشکافی میکند.
درک ناهنجاریها
یک
خواندن فانتوم زمانی رخ میدهد که یک تراکنش، پرسوجویی را با شرط محدوده دوباره اجرا میکند و سطری را پیدا میکند که توسط یک تراکنش دیگر که کامیت شده است، درج شده است. برخلاف خواندن تکرارناپذیر (که در آن یک سطر موجود تغییر میکند)، خواندن فانتوم شامل ظهور سطری جدید «از هوا» است.
انحراف نوشتن اغلب به درک نادرست مواجه میشود. این اتفاق زمانی میافتد که دو تراکنش مجموعههای همپوشان از سطرها را میخوانند، تصمیماتی بر اساس آن خواندهها میگیرند و سپس زیرمجموعههای ناهمپوشان آن سطرها را بهروزرسانی میکنند. وضعیت نهایی یک محدودیت را نقض میکند که اگر تراکنشها به صورت سریالیزه شده بودند، شناسایی میشد. مثالهای کلاسیک شامل برنامهریزی شیفتهای کاری هستند که در آنها بیش از دو نفر نمیتوانند در یک روز خاص کار کنند، یا انتقال وجه بین حسابهایی که در آنها موجودی کل باید مثبت باقی بماند.
PostgreSQL: راهحل قفل محدوده (Gap Lock)
PostgreSQL جداسازی اسنپشات سریالیزهشده (SSI) را پیادهسازی میکند. به طور پیشفرض، آن در سطح Read Committed عمل میکند، اما برای سریالیزاسیون، رویکردی قدرتمند ارائه میدهد. برخلاف MySQL، PostgreSQL از قفلهای محدوده (قفلهایی روی محدودههای ایندکس) استفاده نمیکند. در عوض، آن تضادهای خواندن-نوشتن را تشخیص میدهد.
اگر دو تراکنش کلید یکسانی را بخوانند و یکی از آنها به کلید متفاوتی در همان مجموعه بنویسد، PostgreSQL یک خطای شکست سریالیزاسیون را ایجاد میکند. این امر برنامه را مجبور به تکرار تراکنش میکند. اگرچه این یک جلوگیری مبتنی بر «قفل» واقعی نیست، اما با هزینه تکرارهای گاهبهگاه، سریالیزاسیون را تضمین میکند.
-- Example: Detecting serialization failure in PostgreSQL
BEGIN;
SELECT count(*) FROM orders WHERE status = 'pending';
-- Transaction A updates a row
-- Transaction B also reads and updates
-- PostgreSQL may raise: serialization_failure
COMMIT;
برای کاهش خواندنهای فانتوم بدون سریالیزاسیون کامل، توسعهدهندگان اغلب از
SELECT ... FOR UPDATE استفاده میکنند که سطرهاي خاصی را که بازگردانده شدهاند، قفل میکند. با این حال، این امر سطرهایی را که در آینده با شرط مطابقت دارند قفل نمیکند و خواندن فانتوم را امکانپذیر میگذارد، مگر اینکه با منطق در سطح برنامه یا سطوح جداسازی بالاتر ترکیب شود.
CockroachDB: سریالیزاسیون توزیعشده
CockroachDB از پایه برای سازگاری قوی در سراسر خوشههای توزیعشده ساخته شده است. آن به طور پیشفرض از یک مدل سازگاری خطیپذیر استفاده میکند. این بدان معناست که حتی در سراسر مناطق مختلف، خواندهها جدیدترین نوشتن را بازمیگردانند.
برای خواندنهای فانتوم و انحراف نوشتن، CockroachDB سریالیزاسیون را متفاوت از PostgreSQL مدیریت میکند. آن از یک ساعت منطقی هیبریدی برای مرتبسازی رویدادها استفاده میکند. اگر یک تراکنش ناهنجاری بالقوهای را تشخیص دهد که سریالیزاسیون را نقض میکند، تراکنش را بلافاصله لغو میکند. برخلاف مکانیسم تکرار PostgreSQL که در مرحله کامیت رخ میدهد، CockroachDB میتواند به دلیل ماهیت توزیعشدهاش، تضادها را در حین اجرا تشخیص دهد.
-- CockroachDB serializable example
BEGIN;
-- Read balance
SELECT balance FROM accounts WHERE id = 1;
-- Write to account 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
COMMIT;
-- If another transaction concurrently modified account 1,
-- CockroachDB will abort this transaction with a retryable error.
استراتژیهای عملی
1.
تکرار در سطح برنامه: برای هر دو پایگاهداده، پیچیدن تراکنشهای سریالیزهشده در یک حلقه تکرار با تأخیر نمایی (exponential backoff) ضروری است.
2.
قفلگذاری خوشبینانه: از ستونهای نسخه برای تشخیص زودهنگام تضادها استفاده کنید.
3.
عدم نرمالسازی: گاهی اوقات، انتقال محدودیتها به لایه برنامه یا استفاده از نمایهای متریالیزه شده میتواند رقابت قفل را کاهش دهد.
نتیجهگیری
حل خواندنهای فانتوم و انحراف نوشتن نیازمند تغییر نگرش است. شما دیگر فقط SQL نمینویسید؛ بلکه برای همزمانی طراحی میکنید. PostgreSQL با SSI و تکرارها انعطافپذیری ارائه میدهد، در حالی که CockroachDB سازگاری قوی با تشخیص خودکار تضادها را فراهم میکند. سطح جداسازی را انتخاب کنید که عملکرد را با صحت سختگیرانهای که منطق کسبوکار شما نیاز دارد، متعادل کند.