---
description: 트렌잭션과 무결성
---

# 4.3 Transaction and Integrity

#### 4.3 트랜잭션과 무결성

**4.3.1 트랜잭션 - 원자성, 일관성, 격리성, 지속성**

트랜잭션은 데이터베이스에서 일어나는 작업의 단위로, 이 작업이 완전히 성공하거나 완전히 실패해야 합니다. 이를 보장하기 위해 트랜잭션은 ACID(원자성, 일관성, 격리성, 지속성) 속성을 따릅니다.

* **원자성 (Atomicity)**: 트랜잭션의 모든 작업이 성공적으로 완료되거나, 모두 실패해야 한다는 것을 의미합니다. 예를 들어, 은행 계좌 간의 이체에서 하나의 계좌에서 돈이 인출되고 다른 계좌에 입금되지 않으면, 이 작업 전체가 실패하고 원래 상태로 되돌아가야 합니다.
* **일관성 (Consistency)**: 트랜잭션이 완료되면 데이터베이스가 일관된 상태로 유지되어야 합니다. 이는 모든 데이터 무결성 규칙이 트랜잭션 전후에 유지되어야 함을 의미합니다. 예를 들어, 데이터베이스의 외래 키 제약 조건이 트랜잭션 후에도 만족되어야 합니다.
* **격리성(Isolation)**\
  격리성은 여러 트랜잭션이 동시에 수행될 때, 서로의 작업에 영향을 미치지 않도록 보장합니다. 격리 수준이 높을수록 트랜잭션 간 간섭이 줄어들지만, 그만큼 성능 저하도 발생할 수 있습니다. 격리 수준에는 다음과 같은 단계가 있습니다:
  * **READ UNCOMMITTED**: 다른 트랜잭션에서 커밋되지 않은 변경 사항도 읽을 수 있습니다. (Dirty Read)
  * **READ COMMITTED**: 다른 트랜잭션에서 커밋된 변경 사항만 읽을 수 있습니다.
  * **REPEATABLE READ**: 트랜잭션 내에서 같은 데이터를 여러 번 읽어도 동일한 결과를 반환합니다.
  * **SERIALIZABLE**: 가장 높은 격리 수준으로, 모든 트랜잭션이 순차적으로 실행되는 것처럼 동작합니다.

<details>

<summary>트랜잭션 격리성(Isolation) 수준에 따른 문제점</summary>

#### 더티 리드 (Dirty Read)

**더티 리드**는 하나의 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 상황을 말합니다. 이로 인해 발생할 수 있는 문제는, 읽은 데이터가 나중에 롤백되거나 수정되면 트랜잭션이 잘못된 데이터에 의존하게 된다는 점입니다.

**예시**:

* 트랜잭션 A가 계좌의 잔액을 100에서 50으로 수정했지만, 아직 커밋하지 않았습니다.
* 트랜잭션 B가 이 변경된 잔액 50을 읽었습니다.
* 이후 트랜잭션 A가 롤백되어 잔액이 다시 100으로 돌아갑니다.
* 이 경우, 트랜잭션 B는 실제로 존재하지 않는 50이라는 잘못된 잔액 정보를 기반으로 작업하게 됩니다.

**해결 방법**:\
더티 리드를 방지하려면 최소한 **READ COMMITTED** 수준의 격리성을 사용해야 합니다. 이 격리 수준에서는 트랜잭션이 커밋된 데이터만 읽을 수 있습니다.

#### 논리피터블 리드 (Non-Repeatable Read)

**논리피터블 리드**는 동일한 트랜잭션 내에서 같은 데이터를 두 번 읽었을 때, 그 사이에 다른 트랜잭션이 해당 데이터를 수정하여 읽은 값이 달라지는 현상을 말합니다.

**예시**:

* 트랜잭션 A가 고객의 나이를 처음 읽었을 때 30살이었습니다.
* 트랜잭션 B가 해당 고객의 나이를 31살로 수정하고 커밋합니다.
* 트랜잭션 A가 다시 고객의 나이를 읽었을 때 31살이 됩니다.
* 트랜잭션 A의 관점에서 동일한 트랜잭션 내에서 읽은 데이터가 일관되지 않습니다.

**해결 방법**:\
논리피터블 리드를 방지하려면 **REPEATABLE READ** 수준의 격리성을 사용해야 합니다. 이 격리 수준에서는 트랜잭션 동안 읽은 데이터가 변경되지 않도록 보장합니다.

#### 팬텀 리드 (Phantom Read)

**팬텀 리드**는 트랜잭션 내에서 동일한 쿼리를 두 번 실행했을 때, 그 사이에 다른 트랜잭션이 데이터를 삽입하거나 삭제하여 결과 집합이 달라지는 현상을 말합니다.

**예시**:

* 트랜잭션 A가 "나이 30 이상의 모든 고객"을 조회하여 10명의 고객을 얻었습니다.
* 트랜잭션 B가 나이 30 이상인 새로운 고객을 추가하고 커밋합니다.
* 트랜잭션 A가 다시 같은 쿼리를 실행했을 때, 결과 집합에 11명의 고객이 포함됩니다.

**해결 방법**:\
팬텀 리드를 방지하려면 가장 높은 수준의 격리성인 **SERIALIZABLE**을 사용해야 합니다. 이 격리 수준에서는 모든 트랜잭션이 순차적으로 실행되기 때문에 팬텀 리드가 발생하지 않습니다. 그러나, 성능 저하가 있을 수 있으므로 상황에 맞게 선택해야 합니다.

</details>

* **지속성 (Durability)**: 트랜잭션이 성공적으로 완료된 후에는, 그 결과가 영구적으로 데이터베이스에 반영되어야 합니다. 이는 시스템 오류나 충돌이 발생하더라도 트랜잭션의 결과가 유지된다는 것을 보장합니다.

**4.3.2 무결성**

무결성은 데이터베이스의 데이터가 정확하고 일관되며, 사용자 정의 규칙에 따라 올바르게 유지되는 것을 의미합니다. 데이터 무결성을 보장하기 위해 데이터베이스 시스템은 여러 가지 제약 조건을 지원합니다.

* **도메인 무결성**: 특정 컬럼의 데이터가 허용된 값의 범위 내에 있어야 한다는 것을 보장합니다. 예를 들어, 나이(age) 컬럼에 음수가 입력되지 않도록 하는 제약이 있을 수 있습니다.
* **참조 무결성**: 외래 키 제약 조건에 의해 보장되며, 관계형 데이터베이스에서 두 테이블 간의 참조가 일관성을 유지하도록 합니다. 예를 들어, 주문 테이블의 고객 ID가 고객 테이블에 존재해야 합니다.
* **고유 무결성**: 특정 컬럼의 값이 고유하도록 보장합니다. 예를 들어, 고객 테이블의 이메일 컬럼은 모든 레코드에서 서로 다른 값을 가져야 합니다.
* **엔터티 무결성**: 기본 키 제약 조건에 의해 보장되며, 각 테이블의 레코드가 고유하게 식별될 수 있도록 합니다.



#### 4.3.2 무결성

무결성은 데이터베이스의 데이터가 항상 정확하고 일관성을 유지하도록 하는 제약 조건입니다. 무결성을 유지하기 위해 데이터베이스는 여러 가지 제약을 제공합니다.

1.  **도메인 무결성(Domain Integrity)**\
    특정 컬럼의 값이 미리 정의된 데이터 유형과 제약 조건을 따라야 합니다. 예를 들어, 나이(age) 컬럼에 음수가 입력되지 않도록 하는 제약을 설정할 수 있습니다.

    ```java
    @Column(nullable = false)
    @Min(0)
    private int age;
    ```
2.  **참조 무결성(Referential Integrity)**\
    외래 키(Foreign Key) 관계를 통해 두 테이블 간의 관계가 일관성을 유지하도록 보장합니다. 예를 들어, 주문(Order) 테이블의 고객 ID는 반드시 고객(Customer) 테이블에 존재해야 합니다.

    ```java
    @ManyToOne
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
    ```
3.  **엔터티 무결성(Entity Integrity)**\
    각 테이블의 레코드는 고유한 식별자를 가져야 하며, 기본 키 제약 조건에 의해 보장됩니다. 중복된 기본 키 값이 삽입되지 않도록 합니다.

    ```java
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ```
4.  **고유 무결성(Unique Integrity)**\
    특정 컬럼이나 컬럼 조합의 값이 고유하도록 보장합니다. 예를 들어, 사용자 이메일 주소는 각 사용자마다 고유해야 합니다.

    ```java
    @Column(unique = true)
    private String email;
    ```

무결성 제약을 통해 데이터베이스의 안정성과 신뢰성을 확보할 수 있으며, 데이터가 부정확하게 저장되거나 불필요하게 중복되는 상황을 방지합니다.
