مدیریت تراکنش‌های پایگاه داده در Django: atomic، ATOMIC_REQUESTS، savepointها و رفتار autocommit

این مقاله نحوهٔ مدیریت تراکنش‌ها در Django را توضیح می‌دهد: رفتار پیش‌فرض autocommit، استفاده از ATOMIC_REQUESTS برای تراکنش‌های سطح درخواست، کنترل صریح تراکنش‌ها با atomic، مدیریت خطاها، savepointها، نکات عملکردی، و دلیل استفادهٔ Django از autocommit. همچنین خطرات رایج مانند پنهان‌کردن استثناها داخل atomic و ناسازگاری وضعیت مدل پس از rollback بررسی می‌شود.

transaction، atomic، ATOMIC_REQUESTS، savepoint، autocommitrollback، commit، DatabaseErrorIntegrityError

~8 دقیقه مطالعه • بروزرسانی ۱۹ اسفند ۱۴۰۴

۱. مقدمه

Django ابزارهای قدرتمندی برای مدیریت تراکنش‌های پایگاه داده ارائه می‌دهد. تراکنش‌ها تضمین می‌کنند که مجموعه‌ای از عملیات پایگاه داده یا کاملاً انجام شوند یا هیچ‌کدام انجام نشوند.

۲. رفتار پیش‌فرض Django: حالت autocommit

به‌طور پیش‌فرض، Django در حالت autocommit اجرا می‌شود. یعنی هر کوئری بلافاصله commit می‌شود مگر اینکه داخل یک تراکنش فعال باشد.

Django برای عملیات ORM پیچیده مانند update() و delete() به‌طور خودکار از تراکنش‌ها یا savepointها استفاده می‌کند.

۳. تراکنش‌های سطح درخواست با ATOMIC_REQUESTS

اگر می‌خواهید هر درخواست HTTP داخل یک تراکنش اجرا شود، در تنظیمات دیتابیس مقدار زیر را فعال کنید:


ATOMIC_REQUESTS = True

در این حالت:

  • قبل از اجرای view، یک تراکنش شروع می‌شود.
  • اگر view بدون خطا تمام شود → commit
  • اگر استثنا رخ دهد → rollback

نکات مهم:

  • این روش ساده است اما در ترافیک بالا می‌تواند کند باشد.
  • StreamingHttpResponse خارج از تراکنش اجرا می‌شود.
  • Middleware خارج از تراکنش است.

غیرفعال‌کردن تراکنش برای یک view خاص:


@transaction.non_atomic_requests
def my_view(request):
    ...

۴. کنترل صریح تراکنش‌ها با atomic

atomic() مهم‌ترین ابزار Django برای مدیریت تراکنش‌هاست.

ویژگی‌ها:

  • اگر بلاک بدون خطا تمام شود → commit
  • اگر خطا رخ دهد → rollback
  • قابل استفاده به‌صورت decorator یا context manager
  • قابل تو در تو شدن (nested)

مثال:


@transaction.atomic
def viewfunc(request):
    do_stuff()

استفاده به‌صورت context manager:


with transaction.atomic():
    do_more_stuff()

۵. مدیریت خطاها در atomic

بهترین روش برای مدیریت خطاهای پایگاه داده، قرار دادن atomic داخل try/except است، نه برعکس.

مثال صحیح:


try:
    with transaction.atomic():
        generate_relationships()
except IntegrityError:
    handle_exception()

هشدار مهم:

هرگز استثناهای پایگاه داده را داخل atomic قورت ندهید!

اگر خطا را داخل atomic بگیرید، Django متوجه نمی‌شود که تراکنش خراب شده و ممکن است رفتارهای غیرمنتظره رخ دهد.

۶. rollback و وضعیت مدل

اگر rollback انجام شود، مقدار فیلدهای مدل در حافظه تغییر نمی‌کند. این می‌تواند باعث ناسازگاری شود.

مثال:


obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

برای عملیات جانبی مانند cache، از transaction.on_commit() استفاده کنید.

۷. savepointها و بلاک‌های تو در تو

در atomicهای تو در تو:

  • ورود به بلاک داخلی → ایجاد savepoint
  • خروج موفق → آزادسازی savepoint
  • خروج با خطا → rollback به savepoint

می‌توانید savepoint را غیرفعال کنید:


with transaction.atomic(savepoint=False):
    ...

این کار فقط زمانی توصیه می‌شود که overhead savepointها زیاد باشد.

۸. گزینه durable=True

اگر می‌خواهید atomic همیشه بلاک بیرونی باشد و commit را تضمین کند:


with transaction.atomic(durable=True):
    ...

اگر داخل atomic دیگری باشد → خطای RuntimeError.

۹. autocommit و دلیل استفادهٔ Django از آن

در استاندارد SQL، هر کوئری یک تراکنش جدید ایجاد می‌کند. اما این برای توسعه‌دهندگان سخت است.

به همین دلیل دیتابیس‌ها حالت autocommit ارائه می‌دهند:

  • هر کوئری → تراکنش مستقل
  • در صورت موفقیت → commit
  • در صورت خطا → rollback

Django برخلاف PEP 249، autocommit را به‌طور پیش‌فرض فعال می‌کند تا توسعه ساده‌تر شود.

جمع‌بندی

Django ابزارهای قدرتمندی برای مدیریت تراکنش‌ها ارائه می‌دهد: از atomic برای کنترل دقیق، تا ATOMIC_REQUESTS برای تراکنش‌های سطح درخواست. درک رفتار autocommit، savepointها، مدیریت خطاها و نکات عملکردی برای ساخت اپلیکیشن‌های پایدار و سریع ضروری است.

۱. غیرفعال‌کردن مدیریت تراکنش در Django

می‌توانید مدیریت تراکنش Django را برای یک دیتابیس کاملاً غیرفعال کنید:


AUTOCOMMIT = False

در این حالت:

  • Django هیچ commit خودکاری انجام نمی‌دهد.
  • شما باید تمام تراکنش‌ها را خودتان commit کنید.
  • این روش فقط برای سناریوهای خاص یا middlewareهای سفارشی توصیه می‌شود.

۲. اجرای عملیات پس از commit با on_commit()

گاهی لازم است عملیاتی فقط در صورت موفقیت تراکنش انجام شود، مثل:

  • ارسال ایمیل
  • اجرای job پس‌زمینه
  • پاک‌سازی cache

برای این کار از transaction.on_commit() استفاده کنید:


from django.db import transaction

def send_welcome_email():
    ...

transaction.on_commit(send_welcome_email)

ویژگی‌ها:

  • callback فقط پس از commit موفق اجرا می‌شود.
  • اگر rollback شود، callback حذف می‌شود.
  • اگر تراکنشی فعال نباشد، callback فوراً اجرا می‌شود.
  • با robust=True می‌توانید اجازه دهید خطاها مانع اجرای callbackهای بعدی نشوند.

۳. تعامل on_commit با savepointها

در atomicهای تو در تو، رفتار on_commit دقیق و قابل پیش‌بینی است.

حالت ۱: بدون rollback → همهٔ callbackها اجرا می‌شوند


with transaction.atomic():
    transaction.on_commit(foo)

    with transaction.atomic():
        transaction.on_commit(bar)

# foo() و سپس bar() اجرا می‌شوند

حالت ۲: rollback در savepoint داخلی → callback داخلی حذف می‌شود


with transaction.atomic():
    transaction.on_commit(foo)

    try:
        with transaction.atomic():
            transaction.on_commit(bar)
            raise SomeError()
    except SomeError:
        pass

# فقط foo() اجرا می‌شود

۴. ترتیب اجرا و مدیریت خطا

callbackها به ترتیب ثبت‌شدن اجرا می‌شوند.

اگر callbackی با robust=False خطا بدهد:

  • callbackهای بعدی اجرا نمی‌شوند.
  • تراکنش rollback نمی‌شود (چون callbackها بعد از commit اجرا می‌شوند).

۵. زمان اجرای callbackها

callbackها فقط زمانی اجرا می‌شوند که اتصال دیتابیس دوباره وارد حالت autocommit شود.

وقتی autocommit فعال است و خارج از atomic هستید، callback فوراً اجرا می‌شود.

نکتهٔ مهم: اگر autocommit خاموش باشد و داخل atomic نباشید، فراخوانی on_commit خطا می‌دهد.

۶. استفاده از on_commit در تست‌ها

TestCase هر تست را داخل یک تراکنش اجرا می‌کند و در پایان rollback می‌کند، بنابراین:

  • هیچ commit واقعی انجام نمی‌شود
  • callbackهای on_commit اجرا نمی‌شوند

راه‌حل‌ها:

  • captureOnCommitCallbacks() برای بررسی callbackها
  • TransactionTestCase برای اجرای واقعی commit (کندتر است)

۷. چرا Django rollback hook ندارد؟

rollback hook قابل‌اعتماد نیست، چون rollback ممکن است به دلایل مختلفی رخ دهد، مثل:

  • قطع اتصال دیتابیس
  • کشته‌شدن پردازش

به‌جای انجام کاری و سپس undo کردن آن، Django پیشنهاد می‌کند کار را با on_commit به بعد از commit موکول کنید.

۸. APIهای سطح پایین تراکنش

این APIها فقط زمانی استفاده شوند که atomic کافی نباشد.

کنترل autocommit:


get_autocommit(using=None)
set_autocommit(autocommit, using=None)

نکات:

  • autocommit به‌طور پیش‌فرض روشن است.
  • خاموش‌کردن آن شما را وارد رفتار خام DB-API می‌کند.
  • باید autocommit را خودتان دوباره فعال کنید.
  • قبل از فعال‌کردن autocommit باید تراکنش فعال را commit یا rollback کنید.
  • Django اجازه نمی‌دهد داخل atomic، autocommit را خاموش کنید.

commit و rollback دستی:


commit(using=None)
rollback(using=None)

این توابع داخل atomic قابل استفاده نیستند.

۹. تراکنش چیست؟

تراکنش مجموعه‌ای اتمی از عملیات دیتابیس است. دیتابیس تضمین می‌کند:

  • یا همهٔ تغییرات اعمال می‌شوند
  • یا هیچ‌کدام اعمال نمی‌شوند

برای شروع تراکنش دستی، autocommit را خاموش کنید:


set_autocommit(False)

جمع‌بندی

Django مجموعه‌ای قدرتمند از ابزارهای مدیریت تراکنش ارائه می‌دهد. on_commit اجرای عملیات پس از commit را ایمن می‌کند، savepointها atomicهای تو در تو را مدیریت می‌کنند، و APIهای سطح پایین امکان کنترل کامل را فراهم می‌کنند. درک این ابزارها برای ساخت سیستم‌های پایدار و قابل‌اعتماد ضروری است.

۱. Savepoint چیست؟

Savepoint یک نقطهٔ علامت‌گذاری‌شده داخل یک تراکنش است که اجازه می‌دهد فقط بخشی از تراکنش را rollback کنید، نه کل آن را. این قابلیت در دیتابیس‌های زیر پشتیبانی می‌شود:

  • SQLite
  • PostgreSQL
  • Oracle
  • MySQL (با موتور InnoDB)

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

۲. Savepointها در حالت autocommit

در حالت پیش‌فرض Django (autocommit)، savepointها کاربردی ندارند. اما وقتی داخل atomic() هستید، مجموعه‌ای از عملیات در انتظار commit یا rollback قرار می‌گیرند. در این حالت savepointها امکان rollback جزئی را فراهم می‌کنند.

۳. Savepointها در atomicهای تو در تو

وقتی atomic() تو در تو باشد، Django به‌طور خودکار savepoint ایجاد می‌کند. این کار اجازه می‌دهد بخش داخلی rollback شود بدون اینکه کل تراکنش از بین برود.

با اینکه توصیه می‌شود همیشه از atomic() استفاده کنید، Django همچنان API سطح پایین savepoint را ارائه می‌دهد.

۴. توابع Savepoint در Django

ساخت savepoint:


sid = transaction.savepoint()

commit کردن savepoint:


transaction.savepoint_commit(sid)

rollback به savepoint:


transaction.savepoint_rollback(sid)

پاک‌سازی شمارندهٔ savepoint:


transaction.clean_savepoints()

۵. مثال عملی


@transaction.atomic
def viewfunc(request):
    a.save()

    sid = transaction.savepoint()

    b.save()

    if want_to_keep_b:
        transaction.savepoint_commit(sid)
    else:
        transaction.savepoint_rollback(sid)

۶. کنترل rollback با get_rollback و set_rollback

اگر داخل atomic خطایی رخ دهد، Django کل بلاک را rollback می‌کند—even اگر شما با savepoint آن را مدیریت کرده باشید.

برای کنترل این رفتار:

اجبار rollback:


transaction.set_rollback(True)

جلوگیری از rollback:


transaction.set_rollback(False)

هشدار: قبل از set_rollback(False) باید به یک savepoint سالم rollback کرده باشید، وگرنه atomicity شکسته می‌شود.

۷. نکات دیتابیس‌های مختلف

۷.۱ Savepointها در SQLite

SQLite از savepoint پشتیبانی می‌کند، اما یک مشکل در ماژول sqlite3 باعث محدودیت‌های زیر می‌شود:

  • savepoint فقط داخل atomic قابل استفاده است.
  • وقتی autocommit خاموش باشد، SQLite قبل از اجرای savepoint یک commit ضمنی انجام می‌دهد.
  • نمی‌توان atomic را زمانی که autocommit خاموش است استفاده کرد.

۷.۲ تراکنش‌ها در MySQL

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

  • InnoDB → پشتیبانی کامل
  • MyISAM → بدون تراکنش

اگر تراکنش پشتیبانی نشود، Django همیشه در حالت autocommit کار می‌کند.

۷.۳ مدیریت خطا در PostgreSQL

در PostgreSQL اگر یک خطا رخ دهد (مثلاً IntegrityError)، تمام کوئری‌های بعدی شکست می‌خورند تا زمانی که rollback انجام شود.

روش اول: rollback کامل


a.save()
try:
    b.save()
except IntegrityError:
    transaction.rollback()
c.save()

در این حالت a.save هم از بین می‌رود.

روش دوم: rollback با savepoint


a.save()
sid = transaction.savepoint()
try:
    b.save()
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save()

در این حالت a.save حفظ می‌شود.

جمع‌بندی

Savepointها ابزار قدرتمندی برای rollbackهای جزئی در تراکنش‌ها هستند. Django آن‌ها را به‌طور خودکار در atomicهای تو در تو مدیریت می‌کند، اما API سطح پایین نیز برای کنترل دقیق‌تر در دسترس است. درک تفاوت رفتار دیتابیس‌ها—به‌ویژه SQLite، MySQL و PostgreSQL—برای استفادهٔ صحیح از savepointها ضروری است.

نوشته و پژوهش شده توسط دکتر شاهین صیامی