فرض کنید کاربر وارد سایت شده، یه محصول رو میخره و بکاند باید سه کار انجام بده: سفارش رو ثبت کنه، موجودی محصول رو کم کنه و پرداخت رو ذخیره کنه. دو مرحله اول با موفقیت انجام میشن، ولی دقیقاً موقع ذخیره پرداخت یه Exception میخورید. حالا اگه Transaction نداشته باشید، ممکنه سفارش داخل دیتابیس ثبت شده باشه، موجودی هم کم شده باشه، ولی هیچ پرداختی وجود نداشته باشه؛ یعنی سیستم شما از نظر فنی اجرا شده، ولی دیتابیس دیگه وضعیت منطقی و سالمی نداره.
اینجاست که Transaction وارد ماجرا میشه.
Transaction دقیقاً چه کاری انجام میده؟
Transaction یعنی چند عملیات دیتابیس رو به عنوان یه کار واحد در نظر بگیریم. یا همه مراحل با موفقیت انجام میشن، یا اگر وسط کار مشکلی پیش بیاد، تمام تغییرات برمیگردن به وضعیت قبل.
یعنی به جای این:
ثبت سفارش:موفق
کم کردن موجودی:موفق
ثبت پرداخت: خطا
و باقی موندن دو تغییر قبلی، Transaction میگه اگه مرحله آخر شکست خورد، کل عملیات باید Rollback بشه.
این موضوع تو سیستمهای فروشگاهی، کیف پول، رزرو، مدیریت موجودی و هر جایی که چند تغییر وابسته به هم دارید اهمیت زیادی پیدا میکنه.
مشکل بدون Transaction چه شکلیه؟
فرض کنید چنین Viewای داریم:
def create_order(request):
order = Order.objects.create(
user=request.user,
total_price=500000
)
product = Product.objects.get(id=10)
product.stock -= 1
product.save()
Payment.objects.create(
order=order,
amount=500000
)
return JsonResponse({"success": True})
اگر ساخت Payment به هر دلیلی خطا بده، مثلاً دیتابیس Constraint داشته باشه یا داده نامعتبر باشه، دو عملیات قبلی ممکنه قبلاً ذخیره شده باشن.
در نتیجه یه Order دارید که پرداخت نداره و موجودی محصول هم کم شده. از نگاه کاربر شاید فقط یه Error دیده بشه، ولی پشت صحنه دیتابیس داره اطلاعات خراب جمع میکنه.
transaction.atomic نجاتدهنده اصلیه
Django برای حل این مشکل ابزار خیلی سادهای داره: transaction.atomic().
وقتی عملیات حساس رو داخل یه بلاک Atomic قرار میدید، Django اون بخش رو به عنوان یک Transaction مدیریت میکنه.
from django.db import transaction
def create_order(request):
with transaction.atomic():
order = Order.objects.create(
user=request.user,
total_price=500000
)
product = Product.objects.get(id=10)
product.stock -= 1
product.save()
Payment.objects.create(
order=order,
amount=500000
)
return JsonResponse({"success": True})
حالا اگه هر کدوم از عملیاتهای داخل atomic() Exception ایجاد کنه، Django تغییرات Transaction رو Rollback میکنه. یعنی یا سفارش، موجودی و پرداخت همگی ثبت میشن، یا هیچکدوم ذخیره نمیشن.
همین تغییر ساده میتونه جلوی یه عالمه داده ناسازگار رو بگیره.
هر Query نیاز به Transaction نداره
یکی از اشتباههای رایج اینه که بعد از آشنایی با Transaction بخوایم همه کدهای دیتابیس رو داخل atomic() قرار بدیم.
این کار لازم نیست.
مثلاً یه Query ساده برای گرفتن لیست محصولات یا آپدیت یه فیلد مستقل معمولاً نیازی به Transaction دستی نداره. Transaction زمانی اهمیت پیدا میکنه که چند عملیات به هم وابسته باشن و موفقیت یکی بدون بقیه معنی نداشته باشه.
مثلاً ساخت Order بدون کم شدن موجودی یا انتقال پول از یه حساب بدون اضافه شدن به حساب مقصد، نمونههای واضح این ماجرا هستن.
Transaction طولانی خودش دردسره
Transaction هرچقدر بیشتر باز بمونه، ممکنه Lockها رو بیشتر نگه داره و باعث بشه Queryهای دیگه منتظر بمونن. بنابراین بهتره داخل atomic() فقط عملیاتهایی باشن که واقعاً باید با هم انجام بشن.
مثلاً انجام Request به یه API خارجی، پردازش فایل یا ارسال ایمیل داخل Transaction معمولاً ایده خوبی نیست، چون ممکنه چند ثانیه طول بکشه و در تمام این مدت Transaction باز بمونه.
بهتره Transaction کوتاه و مشخص باشه: تغییرات دیتابیس رو انجام بده، Commit بشه و بعد کارهای جانبی ادامه پیدا کنن.
حواستون به Exceptionها هم باشه
Rollback زمانی اتفاق میفته که Django متوجه خطا بشه. اگر Exception رو داخل بلاک بگیرید و طوری مدیریت کنید که از atomic() خارج نشه، ممکنه رفتار مورد انتظار شما تغییر کنه.
برای همین ساختار مدیریت خطا باید واضح باشه و بهتره عملیات حساس رو طوری طراحی کنید که شکست واقعی باعث Rollback کامل بشه.
نکته اصلی اینه که Transaction فقط یه ابزار نیست؛ باید مرز عملیات تجاری پروژه رو هم درست تعریف کنید.
Transaction برای سیستمهای مالی تقریباً واجبه
هرجا پول، موجودی، رزرو یا اعتبار کاربر وسطه، Partial Save میتونه دردسر بزرگی درست کنه. مثلاً کاربر 100 هزار تومان از کیف پولش کم شده ولی اعتبار مقصد زیاد نشده، یا اتاق رزرو شده ولی پرداخت ثبت نشده.
این نوع باگها معمولاً از اون چیزهایی هستن که شاید تو تستهای ساده دیده نشن، ولی وقتی سیستم واقعی کاربر داشته باشه، پیدا کردن و اصلاح اطلاعات خراب خیلی سختتر از جلوگیری از ایجادشونه.
جمع بندی
Transaction در Django کمک میکنه چند عملیات وابسته دیتابیس رو به عنوان یه عملیات واحد مدیریت کنید. اگر همه مراحل موفق باشن، تغییرات Commit میشن و اگر وسط کار خطایی رخ بده، دیتابیس به وضعیت قبلی برمیگرده.
برای کارهایی مثل ثبت سفارش، پرداخت، مدیریت موجودی، انتقال اعتبار و رزرو، استفاده از transaction.atomic() میتونه جلوی Partial Save و دادههای ناسازگار رو بگیره. فقط یادتون باشه Transaction رو بیدلیل بزرگ نکنید و کارهای کند یا خارجی رو داخلش نگه ندارید.
اگه موفقیت یه Query بدون Query بعدی هیچ معنیای نداره، احتمالاً اون دو باید داخل یه Transaction قرار بگیرن.
نظرات کاربران (0)