ASP.NET 에서의 트랜잭션처리
트랜잭션 모델 선택
트랜잭션 모델을 선택하기 전에 트랜잭션이 필요한지 여부를 고려해야 합니다. 트랜잭션은 서버 응용 프로그램에서 소비되는 가장 비용이 높은 단일 리소스이며 불필요하게 사용할 경우 확장성을 감소시킵니다. 트랜잭션 사용과 관련된 다음 지침을 고려하십시오.
- 일련의 작업에 대해 잠금을 획득하고 ACID 규칙을 강제로 적용해야 하는 경우에만 트랜잭션을 수행합니다.
- 가능한 한 트랜잭션을 짧게 유지하여 데이터베이스 잠금을 유지하는 시간을 최소화합니다.
- 트랜잭션 수명을 클라이언트가 제어할 수 없도록 합니다.
- 개별 SQL 문에 대해서는 트랜잭션을 사용하지 않습니다. SQL Server는 각 문을 개별 트랜잭션으로 자동으로 실행합니다.
자동 트랜잭션과 수동 트랜잭션
자동 트랜잭션을 사용하면 특히 여러 구성 요소가 데이터베이스 업데이트를 수행하는 경우 프로그래밍 모델이 다소 간소화됩니다. 수동 로컬 트랜잭션을 사용하면 Microsoft DTC와의 상호 작용이 필요하지 않기 때문에 항상 속도가 더 빠릅니다. 성능 감소가 줄어들긴 하지만 단일 로컬 리소스 관리자(예: SQL Server)에 대해 자동 트랜잭션을 사용하는 경우, 수동의 로컬 트랜잭션은 DTC와의 불필요한 상호 프로세스 통신(IPC)을 방지하기 때문에 자동 트랜잭션이 수동 트랜잭션보다 느립니다.
다음 경우에 수동 트랜잭션을 사용하십시오.
- 단일 데이터베이스에 대해 트랜잭션을 수행합니다.
다음 경우에 자동 트랜잭션을 사용하십시오.
- 단일 트랜잭션을 여러 원격 데이터베이스로 확장할 수 있어야 합니다.
- 단일 트랜잭션에서 여러 리소스 관리자(예: 데이터베이스 및 Windows 2000 Message Queuing(이전의 MSMQ))를 포함해야 합니다.
참고 트랜잭션 모델은 혼합하지 마십시오. 하나만 선택해서 사용하십시오.
성능이 충분하다고 여겨지는 응용 프로그램 시나리오의 경우에는 프로그래밍 모델을 단순화하기 위해 단일 데이터베이스의 경우라도 자동 트랜잭션을 사용하는 것이 바람직합니다. 자동 트랜잭션을 사용하면 여러 구성 요소에서 동일 트랜잭션의 일부인 작업을 쉽게 수행할 수 있습니다.
수동 트랜잭션 사용
수동 트랜잭션의 경우 구성 요소 코드 또는 저장 프로시저에서 각각 ADO.NET 또는 Transact-SQL의 트랜잭션 지원 기능을 직접 사용하는 코드를 작성합니다. 대부분의 경우 이 방식은 뛰어난 캡슐화 성능을 제공하고 성능적인 관점에서 ADO.NET 코드로 트랜잭션을 수행하는 것과 비슷하기 때문에 저장 프로시저에서 트랜잭션을 제어할 수 있어야 합니다.
ADO.NET에서 수동 트랜잭션 수행
ADO.NET은 새 트랜잭션을 시작하고 커밋 또는 롤백 여부를 명시적으로 제어할 수 있는 트랜잭션을 지원합니다. 트랜잭션 개체는 단일 데이터베이스 연결과 연결되어 있으며 연결 개체의 BeginTransaction 메서드로 얻어집니다. 이 메서드를 호출해도 이후의 명령이 이 트랜잭션의 컨텍스트로 실행되지는 않습니다. 그렇게 하려면 명령의 Transaction 속성을 설정하여 각 명령을 트랜잭션과 명시적으로 연결해야 합니다. 트랜잭션 개체와 여러 명령 개체를 연결하면 단일 트랜잭션의 단일 데이터베이스에 대해 여러 작업을 그룹화합니다.
ADO.NET 트랜잭션 코드 사용 예를 보려면 부록에서 ADO.NET 수동 트랜잭션 코딩 방법을 참조하십시오.
추가 정보
- ADO.NET 수동 트랜잭션의 기본 격리 수준은 Read Committed이므로 데이터가 읽혀지는 동안 데이터베이스가 공유 잠금을 유지하지만 트랜잭션이 끝나기 전에 데이터를 변경할 수 있습니다. 따라서 반복되지 않은 읽기 또는 가상 데이터가 발생할 수 있습니다. 트랜잭션 개체의 IsolationLevel 속성을 IsolationLevel 열거 유형에서 정의된 열거 값 중 하나로 설정하여 격리 수준을 변경할 수 있습니다.
- 트랜잭션에 적합한 격리 수준을 선택할 때는 주의를 기울여야 합니다. 이 때 사용자는 데이터 일관성과 성능 중에서 하나를 선택해야 합니다. 최상의 격리 수준(직렬화)은 절대적인 데이터 일관성을 제공하지만 전반적인 시스템 처리량이 저하됩니다. 격리 수준이 낮으면 응용 프로그램의 확장성이 향상되지만 동시에 데이터 불일치로 인한 오류 발생 가능성이 높아집니다. 데이터 읽기 작업을 수행하고 쓰기 작업은 거의 수행하지 않는 시스템의 경우 낮은 격리 수준이 적합합니다.
- 적합한 트랜잭션 격리 수준 선택에 대한 자세한 내용을 보려면 Microsoft Press 책, Inside SQL Server 2000(저자: Kalen Delaney)을 참조하십시오.
저장 프로시저로 수동 트랜잭션 수행
또한 저장 프로시저에서 Transact-SQL 문을 사용하여 수동 트랜잭션을 직접 제어할 수 있습니다. 예를 들어 BEGIN TRANSACTION, END TRANSACTION 및 ROLLBACK TRANSACTION과 같은 Transact-SQL 트랜잭션 문을 사용하는 단일 저장 프로시저를 사용하여 트랜잭션 작업을 수행할 수 있습니다.
추가 정보
- 필요한 경우 저장 프로시저에서 SET TRANSACTION ISOLATION LEVEL 문을 사용하여 트랜잭션 격리 수준을 제어할 수 있습니다. Read Committed는 SQL Server 기본값입니다. SQL Server 격리 수준에 대한 자세한 내용은 SQL Server 온라인 설명서의 "관계 데이터 액세스 및 변경" 섹션의 격리 수준을 참조하십시오.
- Transact-SQL 트랜잭션 문을 사용하여 트랜잭션 업데이트 수행 방법을 보여 주는 코드 예제를 보려면 부록에 있는 Transact-SQL로 트랜잭션을 수행하는 방법을 참조하십시오.
자동 트랜잭션 사용
자동 트랜잭션은 새로운 트랜잭션을 명시적으로 시작하거나 트랜잭션을 명시적으로 커밋 또는 취소할 필요가 없기 때문에 프로그래밍 모델을 단순화합니다. 하지만 자동 트랜잭션의 가장 중요한 장점은 DTC와 함께 작동할 수 있다는 점입니다. 따라서 단일 트랜잭션을 여러 분산 데이터 원본으로 확장할 수 있습니다. 대규모 분산 응용 프로그램의 경우 이러한 장점은 매우 유용할 수 있습니다. DTC를 직접 프로그래밍하여 분산 트랜잭션을 수동으로 제어할 수 있는 경우라도 자동 트랜잭션은 작업을 크게 간소화할 수 있으며 구성 요소 기반 시스템에 맞게 디자인되었습니다. 예를 들어 단일 트랜잭션으로 구성된 작업을 수행하도록 여러 구성 요소를 선언적으로 쉽게 구성할 수 있습니다.
자동 트랜잭션은 COM+에서 제공되는 분산 트랜잭션 지원 기능에 의존하며, 그 결과 서비스 구성 요소(즉, ServicedComponent 클래스에서 파생된 구성 요소)에서 자동 트랜잭션을 사용할 수 있습니다.
자동 트랜잭션에 대한 클래스를 구성하려면
- System.EnterpriseServices 이름 공간 내에 있는 ServicedComponent 클래스로부터 클래스를 파생합니다.
- Transaction 특성을 사용하여 클래스의 트랜잭션 요구 사항을 정의합니다. TransactionOption 열거 유형으로부터 제공된 값은 클래스가 COM+ 카탈로그에서 구성되는 방법을 결정합니다. 이 특성으로 설정할 수 있는 다른 속성에는 트랜잭션 격리 수준과 제한 시간이 포함됩니다.
- 트랜잭션 결과를 명시적으로 선택해야 하는 경우를 방지하기 위해 AutoComplete 특성으로 메서드를 주석 처리할 수 있습니다. 이러한 메서드가 예외를 발생시키면 트랜잭션이 자동으로 취소됩니다. 필요한 경우 트랜잭션 결과를 직접 선택할 수도 있습니다. 자세한 내용은 이 문서의 후반에 있는 트랜잭션 결과 확인을 참조하십시오.
추가 정보
- COM+ 자동 트랜잭션에 대한 자세한 내용을 보려면 플랫폼 SDK 설명서에서 "COM+를 통한 자동 트랜잭션"을 찾아 보십시오.
- 트랜잭션 .NET 클래스 예를 보려면 부록에 있는 트랜잭션 .NET 클래스를 코딩하는 방법을 참조하십시오.
트랜잭션 격리 수준 구성
COM+ 버전1.0(Windows 2000에서 실행 중인 COM+)에 대한 트랜잭션 격리 수준은 직렬화됩니다. 이 수준은 최고의 격리 수준을 제공하지만 성능 감소를 수반합니다. 관련된 리소스 관리자(일반적으로 데이터베이스)가 트랜잭션 기간 동안 읽기 및 쓰기 잠금을 모두 유지해야 하기 때문에 시스템의 전반적인 처리량이 감소됩니다. 이 기간 동안 다른 모든 트랜잭션이 차단되어 응용 프로그램의 확장 성능에 심각한 영향을 줄 수 있습니다.
Microsoft Windows .NET과 함께 제공되는 COM+ 버전 1.5를 사용하면 트랜잭션 격리 수준을 구성 요소별 기반에 따라 COM+ 카탈로그에 구성할 수 있습니다. 트랜잭션의 루트 구성 요소와 연결된 설정은 트랜잭션의 격리 수준을 결정합니다. 또한 동일 트랜잭션 스트림의 일부인 내부 하위 구성 요소의 트랜잭션 수준은 루트 구성 요소에서 정의된 것보다 높지 않아야 합니다. 격리 수준이 높으면 하위 구성 요소가 인스턴스화될 때 오류가 발생합니다.
.NET 관리 클래스의 경우 Transaction 특성은 공용 Isolation 속성을 지원합니다. 이 속성을 사용하여 다음 코드에 표시된 것과 같이 특정 격리 수준을 선언적으로 지정할 수 있습니다.
[Transaction(TransactionOption.Supported, Isolation=TransactionIsolationLevel.ReadCommitted)]
public class Account : ServicedComponent
{
. . .
}
추가 정보
- 구성 가능한 트랜잭션 격리 수준 및 기타 Windows .NET COM+ 향상 기능에 대한 자세한 내용은 http://msdn.microsoft.com/msdnmag/issues/01/08/ComXP/default.aspx (영문)에서 MSDN Magazine 문서, "Windows XP: COM+ 1.5 혁신 기술로 구성 요소를 더욱 강력하게 만들기"를 참조하십시오
트랜잭션 결과 확인
자동 트랜잭션의 결과는 단일 트랜잭션 스트림의 모든 트랜잭션 구성 요소 컨텍스트에서 일관성 플래그와 함께 트랜잭션 취소 플래그의 상태로 제어됩니다. 트랜잭션 결과는 트랜잭션 스트림의 루트 구성 요소가 비활성화되고 컨트롤이 호출자에게 반환되는 시점에 확인됩니다. 이러한 내용은 기존의 은행 예금 전송 트랜잭션을 보여 주는 그림 5에 설명되어 있습니다.
그림 1.5. 트랜잭션 스트림 및 컨텍스트
트랜잭션의 결과는 루트 개체(이 예에서는 Transfer 개체)가 비활성화되고 클라이언트 메서드 호출이 반환될 때 확인됩니다. 임의의 컨텍스트 내의 일관성 플래그가 False로 설정되었거나 트랜잭션 취소 플래그가 True로 설정된 경우 기본 물리적 DTC 트랜잭션이 취소됩니다.
다음 두 방법 중 하나로 .NET 개체로부터 트랜잭션 결과를 제어할 수 있습니다.
- AutoComplete 특성으로 메서드에 주석을 달고 .NET에서 트랜잭션 결과에 대한 선택 제어를 자동으로 배치합니다. 이 특성을 사용하면 메서드가 예외를 시킬때 일관성 플래그가 자동으로 False로 설정되어 결과적으로 트랜잭션이 취소됩니다. 예외를 발생시지 않고 메서드가 반환되면 일관성 플래그가 True로 설정되어 구성 요소에서 트랜잭션을 커밋해도 된다는 것을 나타냅니다. 이러한 과정은 자동 트랜잭션이 동일 트랜잭션 스트림에서 다른 개체의 선택 사항에 의존하기 때문에 보장되지 않습니다.
- 일관성 플래그를 각각 True 또는 False로 설정하는 ContextUtil 클래스의 정적 SetComplete 또는 SetAbort 메서드를 호출할 수 있습니다.
심각도가 11 이상인 SQL Server 오류가 발생하면 관리 데이터 공급자가 SqlException 유형의 예외가 발생할 수 있습니다. 메서드가 예외를 알리고 처리하는 경우 트랜잭션을 취소하도록 수동으로 선택하거나 [AutoComplete]로 플래깅된 메서드의 경우 예외가 호출자에게 전파되도록 하십시오.
[AutoComplete] 메서드
AutoComplete 특성으로 표시된 메서드의 경우 다음 중 하나를 수행하십시오.
- SqlException을 다시 호출 스택으로 전파합니다.
- 외부 예외에서 SqlException을 래핑하고 이를 호출자에게 전파합니다. 호출자에게 보다 유용한 예외 유형으로 예외를 래핑합니다.
예외를 전파하지 못하면 데이터베이스 오류가 있더라도 개체가 트랜잭션을 취소하도록 선택하지 못할 수 있습니다. 즉, 동일 트랜잭션 스트림을 공유하는 다른 개체에서 수행한 성공적인 작업이 커밋될 수 있습니다.
다음 코드에서는 SqlException을 알리고 이를 호출자에게 직접 전파합니다. 이 개체의 일관성 플래그는 비활성화시에 자동으로 False로 설정되기 때문에 트랜잭션이 결과적으로 취소됩니다.
[AutoComplete]
void SomeMethod()
{
try
{
// Open the connection, and perform database operation
. . .
}
catch (SqlException sqlex )
{
LogException( sqlex ); // Log the exception details
throw; // Rethrow the exception, causing the consistent
// flag to be set to false.
}
finally
{
// Close the database connection
. . .
}
}
비-[AutoComplete] 메서드
AutoComplete 특성으로 표시되지 않은 메서드의 경우 다음을 수행해야 합니다.
- catch 블록 내에서 ContextUtil.SetAbort를 호출하여 트랜잭션을 취소하도록 선택합니다. 이렇게 하면 일관성 플래그가 False로 설정됩니다.
- 예외가 발생하지 않는 경우 ContextUtil.SetComplete를 호출하여 트랜잭션을 커밋하도록 선택합니다. 이렇게 하면 일관성 플래그가 True(기본 상태)로 설정됩니다.
다음 코드에서는 이러한 방식에 대해 설명합니다.
void SomeOtherMethod()
{
try
{
// Open the connection, and perform database operation
. . .
ContextUtil.SetComplete(); // Manually vote to commit the transaction
}
catch (SqlException sqlex)
{
LogException( sqlex ); // Log the exception details
ContextUtil.SetAbort(); // Manually vote to abort the transaction
// Exception is handled at this point and is not propagated to the caller
}
finally
{
// Close the database connection
. . .
}
}
참고 여러 catch 블록이 있는 경우 메서드 시작 시에 ContextUtil.SetAbort를 한 번 호출하고 try 블록의 끝에서 ContextUtil.SetComplete를 호출하는 것이 더 쉽습니다. 이러한 방식에서는 모든 catch 블록 내에서 ContextUtil.SetAbort를 반복해서 호출할 필요가 없습니다. 이러한 메서드로 결정된 일관성 플래그의 설정은 메서드가 반환하는 경우에만 필요합니다
이 방법은 호출 중인 코드에서 트랜잭션이 실패할 것이라는 사실을 인식하도록 만들기 때문에 예외(또는 래핑된 예외)를 호출 스택으로 항상 전파해야 합니다. 이렇게 하면 호출 중인 코드에서 최적화를 수행할 수 있습니다. 예를 들어 은행 예금 전송 시나리오의 경우 전송 구성 요소는 차변 작업이 이미 실패한 경우 대변 작업을 수행하지 않도록 결정할 수 있습니다.
일관성 플래그를 False로 설정한 다음 예외가 발생하지 않고 반환된 경우 호출 중인 코드는 트랜잭션이 실패할 것이라는 사실을 알 수 있는 방법이 없습니다. 부울 값을 반환하거나 부울 출력 매개 변수를 설정할 수 있는 경우에도 일관적으로 예외를 발생시켜 오류 조건을 나타내야 합니다. 이렇게 하면 오류 처리에 대한 표준 방식으로 보다 명확하고 일관적인 코드를 만들 수 있습니다.