프로그래밍/DB

[DB]데이터 충돌 방지: 트랜잭션

다다면체 2025. 1. 15. 10:00
728x90
반응형

데이터베이스는 여러 사용자가 동시에 접근하고 데이터를 수정하는 환경에서 데이터의 무결성과 일관성을 유지하는 것이 매우 중요합니다. 이때 핵심적인 역할을 하는 것이 바로 트랜잭션과 잠금 메커니즘입니다. 이번 글에서는 이 두 가지 개념을 자세히 살펴보고, 실제 상황에서 어떻게 활용되는지 알아보겠습니다.  🚀


반응형

1. 트랜잭션: 데이터 작업의 단위 📦

트랜잭션은 데이터베이스에서 수행되는 일련의 작업들을 하나의 논리적인 단위로 묶는 것을 의미합니다. 마치 하나의 업무 처리 과정과 같습니다. 중요한 것은 트랜잭션은 반드시 ACID 속성을 만족해야 데이터의 신뢰성을 보장할 수 있다는 점입니다. 

  • 원자성 (Atomicity): 트랜잭션 내의 모든 작업은 전부 성공하거나 전부 실패해야 합니다. 부분적인 성공은 허용되지 않습니다. 마치 'All or Nothing'과 같습니다. 예를 들어, 계좌 이체 시 출금과 입금 작업이 모두 성공해야 이체가 완료된 것으로 간주하며, 둘 중 하나라도 실패하면 모든 작업이 취소됩니다.
  • 일관성 (Consistency): 트랜잭션이 완료된 후 데이터베이스는 미리 정의된 규칙(제약 조건)을 준수하는 일관된 상태를 유지해야 합니다. 예를 들어, 잔액은 항상 0 이상이어야 한다는 제약 조건이 있다면, 이체 후에도 이 조건이 만족되어야 합니다.
  • 고립성 (Isolation): 동시에 실행되는 여러 트랜잭션은 서로에게 영향을 주지 않아야 합니다. 각 트랜잭션은 마치 독립적으로 실행되는 것처럼 동작해야 합니다. 이를 통해 데이터의 오염을 방지합니다.
  • 지속성 (Durability): 성공적으로 완료된 트랜잭션의 결과는 영구적으로 데이터베이스에 저장되어야 합니다. 시스템 장애가 발생하더라도 데이터는 유실되지 않아야 합니다.

🚩예제: 트랜잭션의 기본 사용법

-- MySQL에서 트랜잭션 사용
START TRANSACTION;
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;
UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2;
COMMIT;

-- Oracle에서 트랜잭션 사용
BEGIN
    UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;
    UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2;
    COMMIT;
END;

위 예제는 계좌 간 이체 작업을 트랜잭션으로 처리하여 데이터의 일관성을 유지합니다.


2. ACID 속성 심화 탐구 💡

원자성과 일관성: 트랜잭션은 원자성을 통해 데이터의 일관성을 보장합니다. 모든 작업이 성공적으로 완료되지 않으면 롤백(Rollback)을 통해 데이터베이스를 트랜잭션 시작 이전의 상태로 되돌립니다.

 

제약 조건 (Constraints): 데이터의 일관성을 유지하기 위해 데이터베이스에는 다양한 제약 조건을 설정할 수 있습니다. 예를 들어, NOT NULL, UNIQUE, CHECK, FOREIGN KEY 등의 제약 조건을 사용하여 데이터의 유효성을 검사할 수 있습니다.

예제: 제약 조건 위반 방지

-- MySQL에서 제약 조건 적용
ALTER TABLE Accounts ADD CONSTRAINT chk_balance CHECK (Balance >= 0);

-- Oracle에서 제약 조건 적용
ALTER TABLE Accounts ADD CONSTRAINT chk_balance CHECK (Balance >= 0);

위 예제는 잔액이 음수가 되는 것을 방지합니다.


3. 락[Lock] 메커니즘: 동시 접근 제어 🔒

락은 여러 사용자가 동시에 동일한 데이터에 접근하려 할 때 발생할 수 있는 데이터 충돌을 방지하는 핵심적인 메커니즘입니다.

  • 공유 잠금 (Shared Lock - S Lock): 데이터를 읽기 위해 사용되는 잠금입니다. 여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있습니다. 읽기 작업 간에는 충돌이 발생하지 않기 때문입니다.
  • 배타 잠금 (Exclusive Lock - X Lock): 데이터를 수정하기 위해 사용되는 잠금입니다. 하나의 트랜잭션이 데이터를 수정하는 동안 다른 트랜잭션은 해당 데이터에 접근할 수 없습니다. 이를 통해 데이터의 무결성을 보장합니다.

예제: 잠금의 활용

-- MySQL에서 잠금 사용
LOCK TABLES Accounts WRITE;
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;
UNLOCK TABLES;

-- Oracle에서 잠금 사용
SELECT * FROM Accounts WHERE AccountID = 1 FOR UPDATE;

MySQL에서는 LOCK TABLES 명령어를 사용하여 명시적으로 테이블에 잠금을 걸 수 있지만, 일반적으로는 데이터베이스 시스템이 자동으로 관리하는 암시적 잠금을 사용하는 것이 권장됩니다. UPDATE, DELETE, INSERT 등의 DML 구문 실행 시 자동으로 필요한 잠금이 걸립니다.

예제: 암시적 잠금 예제 (MySQL)

START TRANSACTION;
SELECT * FROM Accounts WHERE AccountID = 1 FOR UPDATE; -- AccountID = 1인 행에 배타 잠금 획득
-- 이후 UPDATE 쿼리 실행
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;
COMMIT;

 

FOR UPDATE 구문을 사용하면 해당 행에 배타 잠금을 획득하여 다른 트랜잭션이 해당 행을 수정하지 못하도록 막습니다.


4. 격리 수준: 고립성 보장 🎚️

격리 수준은 트랜잭션 간의 격리 정도를 나타내는 것으로, 어떤 수준의 데이터 오염까지 허용할 것인지를 결정합니다. 격리 수준이 높을수록 데이터 일관성은 높아지지만, 동시성은 낮아질 수 있습니다.

  • Read Uncommitted (RU): 커밋되지 않은 데이터도 읽을 수 있습니다. 가장 낮은 격리 수준으로, 데이터 오염 가능성이 매우 높습니다. (더티 리드 발생 가능)
  • Read Committed (RC): 커밋된 데이터만 읽을 수 있습니다. 대부분의 데이터베이스 시스템에서 기본적으로 사용하는 격리 수준입니다. (더티 리드 방지)
  • Repeatable Read (RR): 동일 트랜잭션 내에서 동일한 데이터를 여러 번 읽어도 항상 같은 결과를 보장합니다. (팬텀 리드 발생 가능)
  • Serializable (SI): 가장 높은 격리 수준으로, 트랜잭션이 순차적으로 실행되는 것과 동일한 효과를 냅니다. 동시성은 가장 낮지만 데이터 일관성은 가장 높습니다.

예제: 격리 수준 설정

-- MySQL에서 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;

-- Oracle에서 격리 수준 설정
ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE;

격리 수준을 적절히 설정하여 데이터 일관성을 유지할 수 있습니다.


5. 실습: 은행 계좌 간 이체 구현 🏦

실습 목표

트랜잭션과 잠금을 활용하여 은행 계좌 간 이체를 안전하게 구현합니다.

CREATE TABLE Accounts (
    AccountID INT PRIMARY KEY,
    Balance DECIMAL(10, 2) NOT NULL DEFAULT 0 CHECK (Balance >= 0) -- 잔액 제약 조건 추가
);

INSERT INTO Accounts (AccountID, Balance) VALUES (1, 1000.00), (2, 500.00);

DELIMITER // -- MySQL에서 프로시저 정의를 위한 구분자 변경

CREATE PROCEDURE TransferMoney(IN fromAccount INT, IN toAccount INT, IN amount DECIMAL(10, 2))
BEGIN
    DECLARE exit handler for sqlexception
    BEGIN
        ROLLBACK;
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '이체 중 오류가 발생했습니다.';
    END;

    START TRANSACTION;
    UPDATE Accounts SET Balance = Balance - amount WHERE AccountID = fromAccount;
    UPDATE Accounts SET Balance = Balance + amount WHERE AccountID = toAccount;

    COMMIT;
END //

DELIMITER ; -- 구분자 복원

CALL TransferMoney(1, 2, 100); -- 계좌 1에서 계좌 2로 100원 이체

SELECT * FROM Accounts;

이 예제는 프로시저를 사용하여 이체 작업을 트랜잭션으로 처리하고, 예외 발생 시 롤백하도록 구현하여 데이터의 안정성을 높였습니다. 또한 잔액 제약 조건을 테이블 생성 시 추가하여 데이터의 일관성을 더욱 강화했습니다.

 


6. 마무리 🌟

트랜잭션과 잠금은 데이터베이스 시스템에서 데이터의 무결성과 일관성을 유지하는 데 필수적인 요소입니다. ACID 속성을 이해하고 적절한 격리 수준과 잠금 메커니즘을 활용함으로써 안정적인 데이터베이스 환경을 구축할 수 있습니다. 질문이 있다면 댓글로 남겨주세요! 😊

 

728x90
반응형