C# 트랜잭션(Transaction): 데이터 무결성을 지키는 법
목차
- 트랜잭션이란?
- C#에서 트랜잭션을 사용하는 이유
- ADO.NET을 활용한 트랜잭션
- Entity Framework에서 트랜잭션 관리
- Dapper를 활용한 트랜잭션
- 트랜잭션 격리 수준
- 트랜잭션을 사용할 때 주의할 점
1. 트랜잭션이란?
트랜잭션(Transaction)은 데이터베이스에서 하나 이상의 작업을 하나의 논리적 단위로 묶는 개념입니다.
트랜잭션은 ACID 속성을 보장하여 데이터의 일관성과 신뢰성을 유지합니다.
ACID 속성
- Atomicity (원자성): 모든 작업이 완전히 수행되거나 전혀 수행되지 않아야 함.
- Consistency (일관성): 트랜잭션이 끝나면 데이터가 유효한 상태를 유지해야 함.
- Isolation (고립성): 트랜잭션이 실행되는 동안 다른 트랜잭션이 간섭하지 않아야 함.
- Durability (지속성): 트랜잭션이 완료되면 데이터는 영구적으로 저장됨.
2. C#에서 트랜잭션을 사용하는 이유
- 은행 계좌 이체: A 계좌에서 출금 후 B 계좌에 입금해야 하는데, 출금만 처리되면 데이터가 불완전해짐.
- 주문 처리 시스템: 고객의 주문과 결제 처리를 하나의 단위로 처리해야 함.
- 분산 시스템: 여러 데이터베이스 작업을 하나의 트랜잭션으로 묶어야 함.
3. ADO.NET을 활용한 트랜잭션
ADO.NET에서 SqlTransaction을 사용하면 SQL Server와의 직접적인 트랜잭션을 제어할 수 있습니다.
예제: 은행 계좌 이체
using System;
using System.Data.SqlClient;
class Program
{
static void Main()
{
string connectionString = "YourConnectionString";
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
try
{
var withdrawCommand = new SqlCommand("UPDATE Accounts SET Balance = Balance - @amount WHERE AccountId = @accountId", connection, transaction);
withdrawCommand.Parameters.AddWithValue("@amount", 100);
withdrawCommand.Parameters.AddWithValue("@accountId", 1);
withdrawCommand.ExecuteNonQuery();
var depositCommand = new SqlCommand("UPDATE Accounts SET Balance = Balance + @amount WHERE AccountId = @accountId", connection, transaction);
depositCommand.Parameters.AddWithValue("@amount", 100);
depositCommand.Parameters.AddWithValue("@accountId", 2);
depositCommand.ExecuteNonQuery();
transaction.Commit();
Console.WriteLine("Transaction committed.");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"Transaction rolled back: {ex.Message}");
}
}
}
}
}
설명
- connection.BeginTransaction()을 통해 트랜잭션 시작.
- SqlCommand에 트랜잭션을 적용하여 SQL 실행.
- 모든 작업이 성공하면 transaction.Commit(), 실패하면 transaction.Rollback().
4. Entity Framework에서 트랜잭션 관리
Entity Framework에서는 SaveChanges()를 호출하면 기본적으로 트랜잭션이 적용됩니다.
하지만 여러 개의 SaveChanges() 호출이 필요할 경우, DbContextTransaction을 사용할 수 있습니다.
예제: EF Core에서 트랜잭션 사용
using (var context = new BankDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
var accountA = context.Accounts.Find(1);
accountA.Balance -= 100;
var accountB = context.Accounts.Find(2);
accountB.Balance += 100;
context.SaveChanges();
transaction.Commit();
Console.WriteLine("Transaction committed.");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"Transaction rolled back: {ex.Message}");
}
}
}
설명
- context.Database.BeginTransaction()을 사용하여 트랜잭션을 시작.
- 모든 작업이 성공적으로 실행되면 Commit().
- 오류 발생 시 Rollback()으로 변경 사항을 되돌림.
5. Dapper를 활용한 트랜잭션
Dapper는 ADO.NET 기반의 마이크로 ORM으로, 성능이 뛰어나면서도 간단한 SQL 트랜잭션 처리를 지원합니다.
예제: Dapper로 트랜잭션 처리
using (var connection = new SqlConnection("YourConnectionString"))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
try
{
connection.Execute("UPDATE Accounts SET Balance = Balance - @amount WHERE AccountId = @accountId",
new { amount = 100, accountId = 1 }, transaction);
connection.Execute("UPDATE Accounts SET Balance = Balance + @amount WHERE AccountId = @accountId",
new { amount = 100, accountId = 2 }, transaction);
transaction.Commit();
Console.WriteLine("Transaction committed.");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"Transaction rolled back: {ex.Message}");
}
}
}
설명
- Execute 메서드를 사용해 SQL 실행.
- transaction 객체를 함께 전달하여 ADO.NET 방식과 동일한 트랜잭션 적용.
6. 트랜잭션 격리 수준
트랜잭션 격리 수준은 다른 트랜잭션과의 상호작용 방식을 결정합니다.
격리 수준 설명 발생 가능 문제
| Read Uncommitted | 다른 트랜잭션의 미완료 데이터 읽기 허용 | Dirty Read |
| Read Committed | Commit된 데이터만 읽기 가능 (기본값) | Non-repeatable Read |
| Repeatable Read | 동일 트랜잭션 내에서 같은 데이터를 여러 번 읽어도 값이 유지됨 | Phantom Read |
| Serializable | 가장 높은 격리 수준, 완전한 트랜잭션 분리 | 성능 저하 |
격리 수준 설정 (SQL Server)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
C#에서 설정
using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
7. 트랜잭션을 사용할 때 주의할 점
- 트랜잭션 범위 최소화: 트랜잭션이 길어지면 데이터베이스 성능이 저하됨.
- 적절한 격리 수준 선택: 높은 격리 수준은 동시성을 줄여 성능에 영향을 미침.
- 예외 처리 필수: 트랜잭션 실패 시 반드시 Rollback() 호출.
- 데드락(Deadlock) 방지: 여러 트랜잭션이 서로의 자원을 기다리는 상황을 피해야 함.
결론
C#에서 트랜잭션을 활용하면 데이터 일관성을 유지하고 동시성 문제를 해결할 수 있습니다.
ADO.NET, Entity Framework, Dapper에서 각각의 트랜잭션 처리 방법을 이해하고, 프로젝트에 적합한 방식을 선택하여 사용하세요! 🚀
.png)
댓글
댓글 쓰기