행을 수정(UPDATE·DELETE·INSERT)하면 그 행에 쓰기 잠금이 걸립니다. 잠금은 COMMIT 이나 ROLLBACK 을 할 때까지 유지됩니다. 같은 행을 수정하려는 다른 세션은 잠금이 풀릴 때까지 기다립니다.
잠긴 행 하나 때문에 세션이 멈춘 것처럼 보이는 것이 잠금 대기입니다. 잠금을 쥔 쪽이 커밋하면 기다리던 세션이 바로 이어 갑니다. 그래서 트랜잭션이 길수록 대기가 길어집니다.
읽기와 잠금의 관계는 DB마다 다릅니다.
| 항목 | Oracle · Tibero | MySQL InnoDB | MSSQL |
|---|---|---|---|
| 일반 SELECT | 잠금 없음 | 잠금 없음 | 기본은 공유 잠금 |
| 읽기가 쓰기를 막나 | 막지 않음 | 막지 않음 | 기본 설정에서는 막을 수 있음 |
| 쓰기가 읽기를 막나 | 막지 않음 | 막지 않음 | 기본 설정에서는 막을 수 있음 |
MSSQL 기본 READ COMMITTED 는 읽을 때도 공유 잠금을 잡아서, 커밋하지 않은 쓰기가 있는 행의 읽기가 기다릴 수 있습니다. READ_COMMITTED_SNAPSHOT 옵션을 켜면 이전 값을 읽어 이 대기가 사라집니다. Oracle 은 읽기가 잠금을 걸지 않아 쓰기를 막지 않습니다.
아래는 예제 데이터(account 3행)로 그린 시간 순서 도식이고 실행 결과가 아닙니다. 두 세션 모두 자동 커밋을 끈 트랜잭션 안에 있습니다.
시간 순서 도식(설명용). 세션 B 가 A 의 잠금 때문에 기다리는 경우입니다.
| 순서 | 세션 A | 세션 B |
|---|---|---|
| 1 | 김대표 잔액을 900 으로 UPDATE | |
| 2 | (COMMIT 하지 않음) | 같은 행을 UPDATE, 기다림 |
| 3 | 30 초 뒤 COMMIT | |
| 4 | 대기 해제, UPDATE 진행 |
B 의 화면은 2번부터 3번까지 멈춰 있습니다. A 가 ROLLBACK 해도 똑같이 풀립니다. 대기 시간 한도는 DB마다 다릅니다.
| DB | 기본 대기 한도 | 한도 초과 |
|---|---|---|
| Oracle · Tibero | 무한(NOWAIT·WAIT n 으로 지정) | 지정했을 때만 오류 |
| MySQL InnoDB | 50 초(innodb_lock_wait_timeout) | 오류 1205 |
| MSSQL | 무한(LOCK_TIMEOUT 기본 -1) | 설정하면 오류 1222 |
MySQL 은 대기 시간 초과가 1205 이고 데드락이 1213 입니다. MSSQL 은 반대로 데드락이 1205 입니다. 같은 1205 라도 DB 가 다르면 뜻이 다르니 오류 번호를 섞어 쓰지 않도록 주의합니다.
데드락은 두 트랜잭션이 서로 상대가 잡은 자원을 기다리는 순환입니다. 어느 한쪽이 양보하지 않으면 영원히 끝나지 않습니다. DB 가 이 순환을 감지하면 한쪽을 골라 오류로 끊습니다.
시간 순서 도식(설명용). 이체 방향이 반대인 두 트랜잭션입니다.
| 순서 | 세션 A (1 → 3 이체) | 세션 B (3 → 1 이체) |
|---|---|---|
| 1 | id 1 UPDATE (1번 행 잠금) | |
| 2 | id 3 UPDATE (3번 행 잠금) | |
| 3 | id 3 UPDATE, B 를 기다림 | |
| 4 | id 1 UPDATE, A 를 기다림 | |
| 5 | 순환 감지, 한쪽이 오류로 끊김 |
3번과 4번에서 A 는 B 를, B 는 A 를 기다립니다. 누구도 COMMIT 을 못 하니 저절로 풀리지 않습니다. DB 는 이 순환을 감지해 한쪽(희생 트랜잭션)을 오류로 끊고, 나머지는 계속 진행합니다.
DB마다 희생 트랜잭션에 남는 상태가 다릅니다. 이 차이를 모르면 오류 뒤에 데이터가 반쯤 남습니다.
| 항목 | Oracle · Tibero | MySQL InnoDB | MSSQL |
|---|---|---|---|
| 오류 | ORA-00060 | 1213 | 1205 |
| 롤백 범위 | 오류 난 문장만 | 트랜잭션 전체 | 트랜잭션 전체 |
| 트랜잭션 | 남아 있음 | 종료됨 | 종료됨 |
Oracle 은 오류 난 문장만 롤백하고 트랜잭션은 남습니다. 애플리케이션이 직접 ROLLBACK 해야 앞 문장의 변경과 잠금이 풀립니다. MySQL 과 MSSQL 은 희생 트랜잭션 전체가 롤백되므로 처음부터 다시 실행하면 됩니다.
핵심데드락 오류는 예외가 아니라 정상 동작입니다. 애플리케이션은 데드락 오류를 잡아 롤백하고 트랜잭션 전체를 몇 번 다시 시도하도록 만듭니다.
DB마다 잠금 단위와 범위가 다릅니다. 필요한 행만 잠기는지, 훨씬 넓게 잠기는지가 대기와 데드락 빈도를 좌우합니다.
정리하면 조건에 맞는 인덱스가 없을수록 더 많은 행이 잠깁니다. 잠금이 넓어지면 서로 부딪힐 확률이 커져서 데드락이 늘어납니다.