SQL에서 트랜잭션은 여러 작업(삽입/업데이트/삭제)을 하나의 원자적 작업 단위로 그룹화할 수 있게 해주며, 이를 완전히 적용하거나 취소해야 합니다. 트랜잭션의 생명 주기는 다음 명령어를 기반으로 합니다:
BEGIN 또는 START TRANSACTION — 트랜잭션 시작;COMMIT — 변경 사항 확정;ROLLBACK — 트랜잭션 내 모든 변경 사항을 되돌림.SQL은 트랜잭션 격리 수준(비확정 읽기, 확정 읽기, 재확정 읽기, 직렬화 가능)을 지원하여 병렬 트랜잭션 간 데이터 가시성을 정의하고 "더러운 읽기" 또는 "팬텀 행"과 같은 문제를 방지합니다.
데이터 무결성을 관리하기 위해서는 다음을 신중히 선택해야 합니다:
SELECT ... FOR UPDATE).PostgreSQL 예제:
BEGIN; -- 상품 행을 선택하고 잠금 설정 SELECT * FROM inventory WHERE id = 1 FOR UPDATE; UPDATE inventory SET quantity = quantity - 1 WHERE id = 1; COMMIT;
일반적으로 인기 있는 DBMS(예: PostgreSQL, MySQL)에서 기본적으로 설정된 격리 수준은 무엇이며, SERIALIZABLE과 어떤 차이가 있나요?
답변:
PostgreSQL에서는 기본적으로 Read Committed 격리 수준이 사용되며, 이 경우 트랜잭션은 요청 시점에 확인된 데이터만 볼 수 있지만 "재확정되지 않은 읽기"(non-repeatable reads)가 발생할 수 있습니다.
MySQL(InnoDB)의 경우는 Repeatable Read입니다. Serializable과의 차이는 후자가 팬텀 행이나 병렬 변경을 완전히 방지하지만, 전역 잠금으로 인해 속도가 눈에 띄게 느리다는 점입니다.
예제:
-- Repeatable Read에서 SELECT는 같은 행을 반환할 수 있지만, Read Committed에서는 트랜잭션 내 두 개의 SELECT 사이에 새로운 행이 나타날 수 있습니다.
역사
대규모 금융 시스템에서 낮은 격리 수준(Read Committed)에서 계좌 간 대규모 이체가 진행되는 동안 동일한 잔액이 여러 트랜잭션에서 동시에 사용되는 상황이 주기적으로 발생했습니다. 이는 자금의 이중 지출(경쟁 상태)을 초래했습니다.
Serializable로 전환하고 잠금을 올바르게 관리한 후 문제는 사라졌습니다.
역사
전자 상거래에서
UPDATE product SET stock = stock - 1트랜잭션을 트랜잭션으로 포장하지 않은 경우 재고보다 더 많은 상품이 판매되는 사태가 발생했습니다. 이 문제는 경쟁 주문이 많을 때에만 나타났습니다. 해결책은 트랜잭션을 사용하고SELECT ... FOR UPDATE를 통해 행을 잠그는 것이었습니다.
역사
물류 시스템에서는 한 테이블에서 빈번한 업데이트 시 명시적 커밋을 잊어버렸습니다. 오류가 발생한 경우 부분 데이터가 자동 커밋이나 잘못된 롤백으로 인해 손실되었습니다. 결과적으로 레코드 손실과 비용이 많이 드는 감사가 발생했습니다.