프로그래밍백엔드 개발자

SQL에서 트랜잭션 작업의 특성에 대해 자세히 설명해주세요. 여러 세션에서 동일한 테이블에 동시 접근할 때 데이터 무결성을 어떻게 관리할 수 있나요?

Hintsage AI 어시스턴트로 면접 통과

답변

SQL에서 트랜잭션은 여러 작업(삽입/업데이트/삭제)을 하나의 원자적 작업 단위로 그룹화할 수 있게 해주며, 이를 완전히 적용하거나 취소해야 합니다. 트랜잭션의 생명 주기는 다음 명령어를 기반으로 합니다:

  • BEGIN 또는 START TRANSACTION — 트랜잭션 시작;
  • COMMIT — 변경 사항 확정;
  • ROLLBACK — 트랜잭션 내 모든 변경 사항을 되돌림.

SQL은 트랜잭션 격리 수준(비확정 읽기, 확정 읽기, 재확정 읽기, 직렬화 가능)을 지원하여 병렬 트랜잭션 간 데이터 가시성을 정의하고 "더러운 읽기" 또는 "팬텀 행"과 같은 문제를 방지합니다.

데이터 무결성을 관리하기 위해서는 다음을 신중히 선택해야 합니다:

  • 격리 수준 선택(예: 은행 응용 프로그램의 경우 — 아마도 Serializable).
  • 트랜잭션에 대한 명시적 관리, 특히 하나의 엔티티가 동시에 수정되는 경우(예: 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를 통해 행을 잠그는 것이었습니다.


역사

물류 시스템에서는 한 테이블에서 빈번한 업데이트 시 명시적 커밋을 잊어버렸습니다. 오류가 발생한 경우 부분 데이터가 자동 커밋이나 잘못된 롤백으로 인해 손실되었습니다. 결과적으로 레코드 손실과 비용이 많이 드는 감사가 발생했습니다.