Database transaction-ACID

Transaction در دیتابیس چیست؟ چرا چند عملیات باید با هم موفق شوند؟

فرض کن کاربر در یک فروشگاه اینترنتی سفارشی ثبت می‌کند.

سیستم باید چند کار انجام دهد:

  1. سفارش را ثبت کند.
  2. موجودی محصول را کاهش دهد.
  3. مبلغ پرداخت را ثبت کند.
  4. وضعیت سفارش را به «پرداخت‌شده» تغییر دهد.

حالا تصور کن سفارش ثبت شده، موجودی هم کم شده، اما هنگام ثبت پرداخت ارتباط با دیتابیس قطع می‌شود.

نتیجه چیست؟ کاربر پول پرداخت کرده، اما سفارش ناقص است.

اینجاست که Transaction اهمیت پیدا می‌کند.

Transaction چیست؟

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

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

به این ترتیب، دیتابیس در وضعیت ناقص باقی نمی‌ماند.

مثلاً:

ثبت سفارش
    ↓
کاهش موجودی
    ↓
ثبت پرداخت
    ↓
تغییر وضعیت سفارش
    ↓
COMMIT

اگر مرحله سوم شکست بخورد:

ثبت سفارش
    ↓
کاهش موجودی
    ↓
ثبت پرداخت ❌
    ↓
ROLLBACK

در این حالت، تغییرات قبلی هم برگردانده می‌شوند.

یک مثال ساده در SQL Server

فرض کنیم دو جدول داریم:

Orders
------
Id
TotalAmount
Status
Products
--------
Id
Name
Stock

حالا می‌خواهیم یک سفارش ثبت کنیم و موجودی محصول را کاهش دهیم.

BEGIN TRANSACTION;

BEGIN TRY

    INSERT INTO Orders (TotalAmount, Status)
    VALUES (100, 'Pending');

    UPDATE Products
    SET Stock = Stock - 1
    WHERE Id = 1;

    COMMIT TRANSACTION;

END TRY
BEGIN CATCH

    ROLLBACK TRANSACTION;
    THROW;

END CATCH;

در این مثال:

  • BEGIN TRANSACTION شروع تراکنش است.
  • COMMIT یعنی همه عملیات موفق بوده‌اند و تغییرات نهایی شوند.
  • ROLLBACK یعنی تغییرات انجام‌شده برگردند.
  • THROW خطا را دوباره به لایه بالاتر ارسال می‌کند.

نکته مهم: Transaction قرار نیست خطا را پنهان کند؛ قرار است جلوی باقی‌ماندن داده‌های ناقص را بگیرد.

ACID چیست؟

Transactionها معمولاً با چهار ویژگی معروف ACID توضیح داده می‌شوند.

Atomicity — اتمیک بودن

همه عملیات یا هیچ‌کدام.

مثلاً اگر ثبت سفارش موفق شود اما کاهش موجودی شکست بخورد، نباید سفارش نهایی باقی بماند.

Consistency — سازگاری

دیتابیس باید از یک وضعیت معتبر به وضعیت معتبر دیگری برود.

مثلاً موجودی محصول نباید به مقدار نامعتبر برسد.

Isolation — جداسازی

تراکنش‌های هم‌زمان نباید باعث شوند داده‌ها به‌صورت نادرست روی هم اثر بگذارند.

مثلاً دو کاربر هم‌زمان نباید بتوانند آخرین موجودی یک محصول را بدون کنترل مناسب خریداری کنند.

Durability — ماندگاری

وقتی Transaction با موفقیت COMMIT شد، تغییرات باید پایدار بمانند؛ حتی اگر بعداً برنامه یا سیستم با مشکل مواجه شود.

Transaction در EF Core

در پروژه‌های ASP.NET Core معمولاً لازم نیست SQL خام بنویسیم. EF Core هم امکان مدیریت Transaction را فراهم می‌کند.

مثلاً:

await using var transaction =
    await dbContext.Database.BeginTransactionAsync();

try
{
    dbContext.Orders.Add(order);

    product.Stock -= 1;

    await dbContext.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

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

اگر SaveChangesAsync() یا هر عملیات دیگری شکست بخورد، می‌توانیم Transaction را Rollback کنیم.

آیا هر SaveChanges خودش Transaction دارد؟

اینجا یک نکته مهم وجود دارد.

در EF Core، وقتی یک SaveChanges معمولی انجام می‌دهید، EF Core معمولاً عملیات تغییر داده‌ها را در یک Transaction قرار می‌دهد.

اما اگر چند مرحله جداگانه داشته باشید، موضوع فرق می‌کند.

مثلاً:

await dbContext.Orders.AddAsync(order);
await dbContext.SaveChangesAsync();

product.Stock -= 1;
await dbContext.SaveChangesAsync();

اگر مرحله اول موفق شود و مرحله دوم شکست بخورد، ممکن است سفارش ثبت شده باشد اما موجودی کاهش پیدا نکرده باشد.

پس Transaction زمانی مهم‌تر می‌شود که چند عملیات مرتبط باید با هم موفق شوند.

یک اشتباه رایج

بعضی وقت‌ها برنامه‌نویس‌ها فکر می‌کنند چون چند عملیات پشت سر هم نوشته شده‌اند، پس همه آن‌ها اتمیک هستند.

مثلاً:

CreateOrder();
ReduceStock();
CreatePayment();

اما این فقط ترتیب اجرای متدهاست؛ Transaction نیست.

اگر بین این عملیات خطایی رخ دهد، دیتابیس لزوماً نمی‌داند که باید همه تغییرات قبلی را برگرداند.

Transaction باید به‌صورت مشخص تعریف شود.

Transaction را کجا مدیریت کنیم؟

در یک پروژه تمیز، بهتر است مدیریت Transaction در لایه‌ای انجام شود که کل عملیات کسب‌وکار را می‌بیند.

مثلاً اگر ثبت سفارش شامل چند Repository باشد، بهتر است Transaction فقط داخل Repository اول باز نشود؛ چون Repository بعدی ممکن است خارج از آن Transaction کار کند.

یک ساختار رایج:

Controller
    ↓
OrderService
    ↓
Transaction
    ↓
OrderRepository
ProductRepository
PaymentRepository

البته در EF Core، اگر همه عملیات با یک DbContext انجام شوند، مدیریت Transaction می‌تواند ساده‌تر باشد.

چه زمانی Transaction لازم نیست؟

قرار نیست هر Query ساده‌ای را داخل Transaction قرار بدهیم.

مثلاً:

SELECT * FROM Products;

برای یک خواندن ساده، معمولاً نیازی به Transaction صریح نداریم.

اما اگر چند عملیات مرتبط دارید که باید همه با هم موفق شوند، Transaction ابزار مناسبی است.

جمع‌بندی

Transaction یکی از پایه‌های مهم طراحی بک‌اند و دیتابیس است.

هر وقت چند عملیات به هم وابسته‌اند، باید از خودمان بپرسیم:

اگر عملیات سوم شکست خورد، آیا می‌خواهم عملیات اول و دوم هم برگردند؟

اگر پاسخ مثبت است، احتمالاً به Transaction نیاز داریم.

در پروژه‌های واقعی، Transaction فقط یک دستور SQL نیست؛ بخشی از طراحی درست عملیات کسب‌وکار است.

داده‌های ناقص معمولاً از یک خطای کوچک شروع می‌شوند؛ Transaction کمک می‌کند همان خطا به مشکل بزرگ‌تری تبدیل نشود.

1 دیدگاه دربارهٔ «Transaction در دیتابیس چیست؟ چرا چند عملیات باید با هم موفق شوند؟»

دیدگاه‌ خود را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

پیمایش به بالا