병행 제어(concurrency control)는 여러 트랜잭션이 같은 때 돌아도 서로를 망치지 않게 차례를 만들어 주는 일입니다. 락을 걸거나 시작 시각으로 순서를 정합니다.
조교 둘이 같은 때 101번 학생의 운영체제(OS) 점수를 올리려 합니다. 한 사람은 5점, 다른 사람은 10점을 올립니다. 차례로 하면 75점이 90점이 됩니다. 그런데 둘이 동시에 75를 읽어 손에 들면, 각자 자기가 든 75에 더해 씁니다. 나중에 쓴 값이 먼저 쓴 값을 덮어, 올린 점수 가운데 하나가 사라집니다. 트랜잭션을 하나씩만 돌리면 이런 일이 없지만, 그러면 모두가 줄을 서서 기다립니다. 병행 제어는 같은 때 돌리면서도 차례로 한 것과 같은 결과를 내게 하는 방법입니다. 아래 그림은 두 트랜잭션 T1·T2를 시간 축 위에 놓고, 제어하지 않은 길과 락을 건 길을 견줍니다.
1같은 값을 둘이 고치면 생기는 일
그림이 먼저 보이는 것은 아무 제어도 하지 않은 길입니다. T1은 5점을, T2는 10점을 올립니다. 101번의 OS 점수는 75이고, 차례로 올리면 90이 되어야 합니다.
T1이 75를 읽어 손에 듭니다. T2도 같은 75를 읽습니다. 이제 둘이 손에 든 값이 같습니다. T1이 손에 든 75에 5를 더해 80을 쓰면 점수 칸이 80이 됩니다. 이어서 T2가 자기가 읽은 75에 10을 더해 85를 씁니다. T1이 쓴 80은 덮여 사라집니다. 결과는 90이 아니라 85입니다. 이렇게 먼저 한 갱신이 사라지는 것을 갱신 분실이라고 합니다.
제어하지 않으면 생기는 일은 넷입니다.
| 문제 | 어떤 일이 벌어지나 |
|---|---|
| 갱신 분실(lost update) | 둘이 같은 값을 읽고 각자 쓰면 나중 쓰기가 먼저 쓰기를 덮어, 먼저 한 갱신이 사라집니다 |
| 현황 파악 오류(dirty read) | 아직 확정되지 않은 중간 값을 읽어 집계나 판단이 틀어집니다 |
| 모순성(inconsistency) | 두 트랜잭션의 문장이 엇갈려 실행되어, 데이터베이스가 어느 쪽 규칙도 지키지 못한 상태가 됩니다 |
| 연쇄 복귀(cascading rollback) | 한 트랜잭션이 되돌아갈 때, 그 중간 값을 읽어 간 다른 트랜잭션까지 함께 되돌려야 합니다 |
갱신 분실은 자료에 따라 갱신 손실이라고도 부릅니다. 현황 파악 오류는 아래 '격리 수준'의 오손 읽기(dirty read)와 같은 것을 가리키는 다른 이름입니다. 모순성은 데이터베이스와 DBMS에서 본 파일 처리의 불일치(inconsistency)와 영문이 같지만 다른 것입니다. 불일치는 같은 데이터가 여러 파일에 중복돼 값이 어긋나는 것이고, 모순성은 두 트랜잭션이 같은 때 도는 것 때문에 생깁니다.
2락과 로킹 단위
그림의 뒤쪽 길은 락을 건 쪽입니다. T1이 읽을 때 그 행에 락을 겁니다. 같은 행을 쓰려는 T2는 락이 풀릴 때까지 기다립니다. T1이 80을 쓰고 끝나며 락을 풀면, 그다음 T2가 80을 읽어 90을 씁니다. 두 갱신이 모두 남습니다.
락(lock)은 어떤 데이터를 쓰는 동안 남이 손대지 못하게 걸어 두는 표시입니다. 두 가지입니다.
| 락 | 함께 걸 수 있는 트랜잭션 | 걸려 있으면 다른 트랜잭션은 |
|---|---|---|
| 공유 락(S, Shared Lock) | 여럿 | 읽을 수 있지만 쓸 수 없습니다 |
| 배타 락(X, Exclusive Lock) | 하나 | 읽지도 쓰지도 못합니다 |
락을 어느 크기에 거는지를 로킹 단위라고 합니다. 데이터베이스 전체, 파일(표), 레코드(행), 필드(열 값) 순으로 작아집니다.
- 단위가 크면 걸어야 할 락이 적어 관리가 쉽고, 한 번에 많은 데이터가 묶여 병행성이 낮아집니다.
- 단위가 작으면 병행성이 높아지고, 락이 많아져 관리가 복잡해집니다.
3병행 제어 기법 넷
그림의 마지막 장면은 T1의 막대를 둘로 나눕니다. 락을 얻기만 하는 앞 단계와 풀기만 하는 뒤 단계로 나누어 지키는 규칙을 2단계 로킹이라고 합니다. 한번 풀기 시작하면 더 얻지 않습니다.
| 기법 | 무엇으로 차례를 정하나 | 교착 상태 |
|---|---|---|
| 로킹(locking) | 먼저 락을 잡은 트랜잭션이 먼저 | 생길 수 있습니다 |
| 2단계 로킹(2PL, Two-Phase Locking) | 락을 다 얻은 뒤에야 풀기 시작한다는 규칙으로 | 생길 수 있습니다 |
| 타임스탬프 순서(timestamp ordering) | 트랜잭션마다 붙인 시작 시각의 순서로 | 생기지 않습니다 |
| 낙관적 검증(optimistic validation) | 끝낼 때 검사해 충돌한 쪽을 철회해서 | 생기지 않습니다 |
- 2단계 로킹은 락을 얻기만 하는 확장 단계(growing phase)와 풀기만 하는 축소 단계(shrinking phase)로 나눕니다. 이 규칙을 지키면 차례로 실행한 것과 같은 결과가 나오는 것이 보장됩니다. 다만 교착 상태는 막지 못합니다.
- 타임스탬프 순서는 락을 쓰지 않습니다. 시작 시각의 순서를 어기는 접근이 오면 그 트랜잭션을 철회합니다.
- 낙관적 검증은 트랜잭션이 도는 동안 아무 검사도 하지 않고 자기 사본에만 씁니다. 끝낼 때 다른 트랜잭션과 충돌했는지 검증해, 충돌이 없으면 반영하고 있으면 철회합니다. 충돌이 드문 곳에 맞습니다.
교착 상태(deadlock)는 두 트랜잭션이 서로가 가진 락을 기다려 둘 다 멈추는 것입니다. DBMS는 교착 상태를 찾아내 한쪽을 철회해 풀어 줍니다.
4격리 수준과 읽기 이상
격리 수준(isolation level)은 끝나지 않은 트랜잭션의 변경을 다른 트랜잭션에 어디까지 보일지 정하는 설정입니다. 수준을 올리면 이상한 읽기를 더 막고, 그만큼 기다리는 일이 늘어납니다.
읽기 이상은 셋입니다.
- 오손 읽기(dirty read): 다른 트랜잭션이 아직 확정하지 않은 값을 읽습니다.
- 반복 불가능 읽기(non-repeatable read): 같은 행을 두 번 읽었는데, 그사이 다른 트랜잭션이 고쳐 값이 달라집니다.
- 유령 읽기(phantom read): 같은 조건으로 두 번 읽었는데, 그사이 다른 트랜잭션이 행을 넣어 없던 행이 나타납니다.
| 격리 수준 | 오손 읽기 | 반복 불가능 읽기 | 유령 읽기 |
|---|---|---|---|
| READ UNCOMMITTED | 생깁니다 | 생깁니다 | 생깁니다 |
| READ COMMITTED | 막습니다 | 생깁니다 | 생깁니다 |
| REPEATABLE READ | 막습니다 | 막습니다 | 생깁니다 |
| SERIALIZABLE | 막습니다 | 막습니다 | 막습니다 |
DBMS마다 받는 수준이 다릅니다. Oracle은 READ COMMITTED(기본값)와 SERIALIZABLE 둘만 받고, 나머지를 쓰면 받는 값을 알려 주는 오류를 냅니다.
ORA-02179: valid options:
ISOLATION LEVEL
{ SERIALIZABLE | READ
COMMITTED }
예제
락을 직접 걸어 본다
보통은 DBMS가 알아서 락을 걸지만, 읽으면서 직접 걸어 둘 수도 있습니다.
FOR UPDATE를 붙이면 읽은 행에 배타 락이 걸립니다.
SELECT 학번,
점수
FROM 수강
WHERE 과목코드 = 'OS'
ORDER BY 학번
FOR UPDATE;
| 학번 | 점수 |
|---|---|
| 101 | 75 |
| 102 | 60 |
| 104 | 75 |
읽어 온 세 행에 락이 걸렸습니다. 트랜잭션이 COMMIT이나 ROLLBACK으로 끝날 때까지 다른 트랜잭션은 이 세 행을 고치지 못합니다. 점수를 읽고 계산해 다시 쓰는 일을 할 때, 읽는 순간부터 락을 걸어 두면 갱신 분실을 막을 수 있습니다.
두 세션이 겹치는 장면 — 교착 상태
두 트랜잭션이 같은 때 도는 장면은 혼자 돌려 볼 수 없습니다. 시각 순서로 적은 표로 봅니다.
| 시각 | T1 | T2 |
|---|---|---|
| 1 | 101번 행에 락을 걸고 고친다 | |
| 2 | 102번 행에 락을 걸고 고친다 | |
| 3 | 102번 행을 고치려고 기다린다 | |
| 4 | 101번 행을 고치려고 기다린다 |
- 시각 3에서 T1이 기다리는 102번 행의 락은 T2가 쥐고 있습니다. T2는 트랜잭션이 끝날 때까지 놓지 않습니다.
- 시각 4에서 T2가 기다리는 101번 행의 락은 T1이 쥐고 있습니다. T1은 시각 3부터 멈춰 있어 끝낼 수 없습니다.
- 둘 다 상대가 놓기를 기다려 영원히 멈춥니다. 이것이 교착 상태입니다.
- 두 트랜잭션이 행을 같은 순서(101번 다음 102번)로 잡았다면 생기지 않습니다. 락을 잡는 순서를 맞추는 것이 교착 상태를 줄이는 방법 가운데 하나입니다.
시험 포인트
- 필기: 병행 제어를 하지 않으면 갱신 분실·현황 파악 오류·모순성·연쇄 복귀가 생깁니다. 락은 함께 걸 수 있는 공유 락과 하나만 거는 배타 락 둘입니다. 로킹 단위가 크면 락이 적어 관리는 쉽고 병행성은 낮아지고, 작으면 그 반대입니다. 2단계 로킹은 락을 얻기만 하는 확장 단계와 풀기만 하는 축소 단계로 나누어, 차례로 실행한 것과 같은 결과를 보장하지만 교착 상태는 막지 못합니다. 타임스탬프 순서와 낙관적 검증은 락을 쓰지 않아 교착 상태가 생기지 않습니다. 격리 수준은 READ UNCOMMITTED·READ COMMITTED·REPEATABLE READ·SERIALIZABLE 넷이고, 수준이 올라갈수록 읽기 이상을 더 막습니다.
- 실기: 로킹(Locking)·2단계 로킹(2PL, Two-Phase Locking)·타임스탬프 순서(Timestamp Ordering)·낙관적 검증(Optimistic Validation), 갱신 분실(Lost Update)입니다. 갱신 분실은 갱신 손실로도 씁니다.
확인 문제
1. 어떤 시스템이 로킹 단위를 레코드(행)에서 파일(표)로 키웠습니다. 바뀌는 것을 고르세요.
- ① 걸어야 할 락이 늘어 관리가 복잡해지고 병행성은 높아진다
- ② 걸어야 할 락이 늘어 관리가 복잡해지고 병행성은 낮아진다
- ③ 걸어야 할 락이 줄어 관리가 쉬워지고 병행성은 높아진다
- ④ 걸어야 할 락이 줄어 관리가 쉬워지고 병행성은 낮아진다
정답
④입니다. 표 하나에 락 하나를 걸면 그 표의 모든 행을 한 번에 묶으므로 걸어야 할 락이 줄어 관리가 쉬워집니다. 그만큼 다른 트랜잭션이 같은 표의 다른 행에도 손대지 못해 병행성은 낮아집니다.
- ① 단위를 키우면 락이 늘어난다고 봤습니다. 단위가 커지면 묶는 덩어리가 커져 락 수는 줄어듭니다.
- ② 락 수는 거꾸로 봤고 병행성은 맞게 봤습니다. 단위가 커지면 락 수는 줄어듭니다.
- ③ 관리가 쉬워지면 병행성도 좋아진다고 봤습니다. 둘은 함께 좋아지지 않고 서로 바꿔 가지는 관계입니다.
2. 다음 (가)~(다)에 맞는 이름을 한글 이름과 영문으로 함께 쓰세요. 영문 약어가 있으면 약어도 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 두 창구에서 같은 학생의 학점 합계를 각각 읽어 자기가 읽은 값에 더해 저장했습니다. 나중에 저장한 쪽의 값만 남아, 먼저 더한 몫이 없어졌습니다.
- (나) 어떤 행을 고치려는 트랜잭션이 그 행에 표시를 걸어 두고, 끝날 때 표시를 거둡니다. 표시가 걸린 동안 다른 트랜잭션은 그 행을 건드리지 못합니다.
- (다) (나)의 표시를 거두기 시작한 뒤에는 새 표시를 더 걸지 못하게 정한 규칙입니다. 걸기만 하는 앞 단계와 거두기만 하는 뒤 단계로 나뉩니다.
정답
받는 답: (가) 갱신 분실(Lost Update) · (나) 로킹(Locking) · (다) 2단계 로킹(2PL, Two-Phase Locking)
(가)의 실마리는 '각자 읽은 값에 더해 저장해 한쪽 몫이 없어졌다', (나)는 '고치는 동안 표시를 걸어 남을 막는다', (다)는 '거두기 시작하면 더 걸지 못한다는 규칙'입니다.
- 같은 이름의 다른 꼴: (가) 갱신 손실 — 자료에 따라 이렇게도 부릅니다. 함께 받습니다.
- 틀리는 꼴 연쇄 복귀 — (가)에: 한 트랜잭션이 되돌아갈 때 그 값을 읽어 간 다른 트랜잭션까지 되돌리는 일입니다. (가)에는 되돌리는 일이 없습니다.
- 틀리는 꼴 배타 락 — (나)에: 락의 한 가지 종류 이름입니다. (나)가 물은 것은 락을 걸고 푸는 기법의 이름입니다.
- 틀리는 꼴 확장 단계 — (다)에: 2단계 로킹의 앞 단계 이름입니다. (다)가 물은 것은 두 단계로 나눈 규칙 전체의 이름입니다.
3. 다음 (가)·(나)에 맞는 병행 제어 기법의 이름을 한글 이름과 영문으로 함께 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 트랜잭션이 들어올 때 번호표처럼 시작 시각을 붙여 둡니다. 데이터에 접근할 때 그 시각의 앞뒤를 따져, 붙인 순서를 어기는 접근이면 그 트랜잭션을 철회합니다.
- (나) 트랜잭션이 도는 동안에는 아무 검사도 하지 않고 자기 사본에만 씁니다. 끝낼 때가 되어서야 다른 트랜잭션과 부딪혔는지 따져, 부딪히지 않았으면 그때 반영합니다.
정답
받는 답: (가) 타임스탬프 순서(Timestamp Ordering) · (나) 낙관적 검증(Optimistic Validation)
(가)의 실마리는 '시작 시각을 붙여 그 순서로만 받아 준다', (나)는 '도는 동안 검사하지 않고 끝낼 때 따진다'입니다. 둘 다 락을 쓰지 않아 교착 상태가 생기지 않습니다.
- 틀리는 꼴 낙관적 검증 — (가)에: 끝낼 때 충돌을 검사해 철회하는 기법입니다. (가)는 들어올 때 붙인 시각으로 접근 순서를 미리 정합니다.
- 틀리는 꼴 타임스탬프 순서 — (나)에: 시작 시각의 순서로 접근을 받아 주는 기법입니다. (나)는 시각을 보지 않고 끝낼 때 충돌만 따집니다.
- 틀리는 꼴 2단계 로킹 — (나)에: 락을 얻는 단계와 푸는 단계로 나눈 규칙입니다. (나)에는 락이 없습니다.
4. 격리 수준을 READ COMMITTED로 둔 트랜잭션이 학과 표의 같은 행을 두 번 읽습니다. 두 읽기 사이에 다른 트랜잭션이 그 행의 건물을 고치고 COMMIT했습니다. 이 트랜잭션에 생길 수 있는 일을 고르세요.
- ① 두 번째 읽기에도 첫 번째와 같은 건물이 나온다
- ② 두 번째 읽기에 고쳐진 건물이 나온다
- ③ 두 번째 읽기에 아직 확정되지 않은 건물이 나온다
- ④ 두 번째 읽기가 앞 읽기와 어긋나 오류로 막힌다
정답
②입니다. READ COMMITTED는 확정된 값만 읽게 해 오손 읽기를 막습니다. 그러나 두 읽기 사이에 남이 고치고 확정한 것까지 가려 주지는 않아, 같은 행을 다시 읽으면 값이 달라집니다. 반복 불가능 읽기입니다.
- ① 두 읽기의 값이 같게 유지되는 것은 REPEATABLE READ부터입니다.
- ③ 확정되지 않은 값을 읽는 것이 오손 읽기이고, READ COMMITTED가 막는 것이 바로 이것입니다.
- ④ 격리 수준은 읽기를 오류로 막지 않습니다. 무엇을 보여 줄지만 정합니다.