مدیریت قفلهای پایگاه داده در لاراول: 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
این کد ممکن است در شرایط عادی بهدرستی عمل کند، اما در مواردی که درخواستهای همزمان دریافت میشود، ممکن است مشکلاتی ایجاد شود.
برای اطمینان از اجرای کامل یا بازگشت تمام مراحل یک عملیات، باید از تراکنشها استفاده کنید:
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; }); } }
در صورت بروز هرگونه خطا، تراکنش بهطور کامل بازگشت داده میشود و هیچ تغییری اعمال نمیشود.
تراکنشها ممکن است مشکل خواندن دادههای قدیمی را حل نکنند. برای حل این مشکل، باید از قفلهای بدبینانه استفاده کنید. در لاراول، میتوانید از 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 });
برای جلوگیری از مشکلاتی مانند Double Spending، استفاده از lockForUpdate کافی است، زیرا این قفل مانع از خواندن یا تغییر همزمان رکوردها میشود.
استفاده از قفلهای مناسب در لاراول، مانند lockForUpdate و sharedLock، به شما کمک میکند تا تراکنشهای ایمن و بدون تناقض را پیادهسازی کنید. با این حال، برای سیستمهای بزرگتر مانند بانکداری، استفاده از روشهای پیشرفتهتر مانند Event Sourcing توصیه میشود.
این ابزارها به شما کمک میکنند تا از مشکلاتی مانند دوبرابر شدن یا از دست رفتن دادهها جلوگیری کنید و سیستمهایی قابل اعتمادتر بسازید.