زهیر مظاهری
زهیر مظاهری
خواندن ۳ دقیقه·۸ روز پیش

قفل‌های پایگاه داده در لاراول

مدیریت قفل‌های پایگاه داده در لاراول: lockForUpdate و sharedLock

گاهی اوقات برای مدیریت داده‌های حساس و جلوگیری از مشکلاتی مانند تناقض‌های احتمالی، نیاز به استفاده از قفل‌های پایگاه داده دارید. در این مقاله به بررسی دو قابلیت مهم لاراول، lockForUpdate و sharedLock، می‌پردازیم که به شما کمک می‌کنند تا این چالش‌ها را به شکلی بهینه مدیریت کنید.

مشکل: به‌روزرسانی‌های ناسازگار در پایگاه داده

تصور کنید یک سیستم بانکی داریم که مشتریان می‌توانند به‌راحتی بین حساب‌ها تراکنش انجام دهند. بدون قفل‌های مناسب، ممکن است مشکلاتی مانند دوبار خرج کردن (Double Spending) رخ دهد.

برای مثال، اگر چندین درخواست همزمان ارسال شوند و هر دو تراکنش وضعیت موجودی یکسانی را بخوانند، ممکن است سیستم بیش از موجودی واقعی، پول جابه‌جا کند.

پیاده‌سازی اولیه: بدون قفل

مدل کاربر و تابع انتقال وجه بین حساب‌ها به‌صورت زیر تعریف شده است:

namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $fillable = [ 'name', 'balance', ]; protected $casts = [ 'balance' => 'float', ]; } class Bank { public static function sendMoney(User $from, User $to, float $amount) { if ($from->balance < $amount) { return false; } $from->update(['balance' => $from->balance - $amount]); $to->update(['balance' => $to->balance + $amount]); return true; } }


در ابتدا، ارسال پول بین حساب‌ها به‌سادگی انجام می‌شود:

$alice = User::find(1); // 'balance' => 100 $bob = User::find(2); // 'balance' => 0 Bank::sendMoney($alice, $bob, 100); // true

این کد ممکن است در شرایط عادی به‌درستی عمل کند، اما در مواردی که درخواست‌های همزمان دریافت می‌شود، ممکن است مشکلاتی ایجاد شود.

راه‌حل اول: تراکنش‌های اتمی (Atomic Transactions)

برای اطمینان از اجرای کامل یا بازگشت تمام مراحل یک عملیات، باید از تراکنش‌ها استفاده کنید:

use Illuminate\Support\Facades\DB; class Bank { public static function sendMoney(User $from, User $to, float $amount) { if ($from->balance < $amount) { return false; } return DB::transaction(function () use ($from, $to, $amount) { $from->update(['balance' => $from->balance - $amount]); $to->update(['balance' => $to->balance + $amount]); return true; }); } }

در صورت بروز هرگونه خطا، تراکنش به‌طور کامل بازگشت داده می‌شود و هیچ تغییری اعمال نمی‌شود.

راه‌حل دوم: قفل‌های بدبینانه (Pessimistic Locking)

تراکنش‌ها ممکن است مشکل خواندن داده‌های قدیمی را حل نکنند. برای حل این مشکل، باید از قفل‌های بدبینانه استفاده کنید. در لاراول، می‌توانید از lockForUpdate برای جلوگیری از دسترسی همزمان به رکوردها استفاده کنید:

DB::transaction(function () { $alice = User::lockForUpdate()->find(1); // 'balance' => 100 $bob = User::lockForUpdate()->find(2); // 'balance' => 0 Bank::sendMoney($alice, $bob, 100); // true }); DB::transaction(function () { $alice = User::lockForUpdate()->find(1); // 'balance' => 0 $charlie = User::lockForUpdate()->find(3); // 'balance' => 0 Bank::sendMoney($alice, $charlie, 100); // false });

تفاوت lockForUpdate و sharedLock

  • متد lockForUpdate: مانع از تغییر یا خوانده شدن رکورد توسط سایر تراکنش‌ها تا زمان اتمام تراکنش جاری می‌شود.
  • متد sharedLock: فقط مانع از تغییر رکورد می‌شود، اما سایر تراکنش‌ها می‌توانند آن را بخوانند.

برای جلوگیری از مشکلاتی مانند Double Spending، استفاده از lockForUpdate کافی است، زیرا این قفل مانع از خواندن یا تغییر همزمان رکوردها می‌شود.

نتیجه‌گیری

استفاده از قفل‌های مناسب در لاراول، مانند lockForUpdate و sharedLock، به شما کمک می‌کند تا تراکنش‌های ایمن و بدون تناقض را پیاده‌سازی کنید. با این حال، برای سیستم‌های بزرگ‌تر مانند بانکداری، استفاده از روش‌های پیشرفته‌تر مانند Event Sourcing توصیه می‌شود.

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

laraveldatabaseacidsoftware engineering
شاید از این پست‌ها خوشتان بیاید