DML이 수정하는 행에 걸립니다. 같은 행을 고치려는 다른 세션만 대기합니다.
이 파트에서 다루는 내용
Oracle의 락은 행 단위이고 승격되지 않습니다
Oracle은 수정하는 행에만 락을 겁니다. 그리고 행 락이 많아졌다고 해서 테이블 락으로 바꾸지 않습니다.
MSSQL에는 락이 일정 수를 넘으면 테이블 락으로 승격하는 동작이 있어서, 대량 수정이 갑자기 테이블 전체를 막는 일이 생깁니다. Oracle에는 그런 승격이 없습니다. 이관 시 동시성 특성이 달라지는 지점입니다.
다만 잠금은 커밋이나 롤백까지 유지되고 기본적으로 시간 제한이 없습니다. 즉 누군가 커밋하지 않으면 상대는 무한정 기다립니다.
DML 중에 그 테이블의 구조가 바뀌지 않도록 거는 약한 락입니다. 다른 DML은 막지 않고 DDL만 막습니다.
행 락이 아무리 많아져도 테이블 락이 되지 않습니다. 락 정보를 데이터 블록에 함께 두는 구조라 개수 제한이 없습니다.
기본적으로 타임아웃이 없습니다. 대기 세션이 스스로 포기하게 하려면 FOR UPDATE WAIT n 이나 애플리케이션 타임아웃이 필요합니다.
누가 누구를 막고 있는지 찾습니다
화면이 멈췄다는 신고가 들어오면 가장 먼저 확인할 것입니다. Oracle은 대기 세션이 어떤 세션 때문에 막혀 있는지 직접 알려 줍니다.
SELECT s.sid,
s.serial#,
s.username,
s.status,
s.event, -- enq: TX - row lock contention
s.seconds_in_wait,
s.blocking_session AS blocker_sid,
s.machine,
s.program
FROM v$session s
WHERE s.blocking_session IS NOT NULL
ORDER BY s.seconds_in_wait DESC;blocking_session 이 원인 세션의 SID입니다. event 가 enq: TX - row lock contention 이면 행 락 대기입니다. seconds_in_wait 으로 얼마나 오래 막혔는지 봅니다.
SELECT s.sid, s.serial#, s.username, s.status,
s.last_call_et AS idle_sec, -- 마지막 작업 이후 경과 시간
s.machine, s.program,
q.sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id = s.prev_sql_id
WHERE s.sid = :blocker_sid;status 가 INACTIVE 이고 idle_sec 이 크면 사람이 커밋하지 않고 자리를 비운 경우입니다. ACTIVE 면 실제로 긴 작업이 도는 중이므로 끊기 전에 담당자를 먼저 확인합니다.
-- 확인 후에만 실행합니다
ALTER SYSTEM KILL SESSION '123,45678' IMMEDIATE;끊으면 그 세션의 트랜잭션이 롤백됩니다. 되돌릴 양이 많으면 롤백에도 시간이 걸립니다. 무엇을 하던 세션인지 확인하지 않고 끊으면 업무 데이터가 통째로 사라질 수 있으므로, 운영에서는 담당자 확인을 원칙으로 합니다.
데드락은 잠그는 순서가 엇갈릴 때 생깁니다
A 세션이 1번 행을 잡고 2번 행을 기다리는데, B 세션이 2번 행을 잡고 1번 행을 기다리면 둘 다 영원히 못 나갑니다.
Oracle은 이걸 자동으로 감지해 한쪽에 ORA-00060 을 냅니다. 여기서 중요한 것이 있습니다. Oracle은 트랜잭션 전체를 롤백하지 않고 데드락을 유발한 그 문장 하나만 되돌립니다.
그래서 애플리케이션이 이 오류를 잡아 처리하지 않으면, 트랜잭션은 살아 있는데 일부만 실행된 어중간한 상태로 남습니다. 오류를 받으면 명시적으로 롤백하고 재시도해야 합니다.
여러 행이나 여러 테이블을 다룰 때 항상 같은 순서로 잠급니다. 정렬해서 처리하는 것만으로 대부분 해결됩니다.
잠그고 있는 시간이 짧을수록 엇갈릴 확률이 줄어듭니다.
자식 테이블의 FK 컬럼에 인덱스가 없으면, 부모 행을 삭제하거나 키를 수정할 때 Oracle이 자식 테이블에 락을 겁니다. 원인을 찾기 어려운 데드락의 대표적인 출처입니다.
데드락이 발생하면 alert log에 기록되고 트레이스 파일이 남습니다. 어떤 SQL이 엇갈렸는지 여기서 확인합니다.
SELECT c.table_name,
c.constraint_name,
cc.column_name
FROM user_constraints c
JOIN user_cons_columns cc
ON cc.constraint_name = c.constraint_name
WHERE c.constraint_type = 'R'
AND NOT EXISTS (
SELECT 1
FROM user_ind_columns ic
WHERE ic.table_name = c.table_name
AND ic.column_name = cc.column_name
AND ic.column_position = 1
)
ORDER BY c.table_name;여기 나온 FK 컬럼에는 인덱스를 만드는 것을 검토합니다. 부모 삭제가 전혀 없는 마스터 테이블이라면 그대로 둬도 되지만, 대부분은 만들어 두는 편이 안전합니다.
배치가 온라인 화면을 멈추는 상황
야간 배치가 대량 UPDATE를 한 트랜잭션으로 돌리면 그 행들이 전부 잠깁니다. 배치가 도는 동안 같은 데이터를 쓰는 온라인 업무가 막힙니다.
낮 시간에 대량 정정 작업을 요청받았을 때 특히 위험합니다. 한 번에 실행하면 그 순간 업무가 멈춥니다.
- 커밋 단위를 쪼갭니다. 수천 건 단위로 나누면 각 구간이 끝날 때마다 락이 풀립니다.
- 쪼갠 만큼 중간 실패 시 상태가 어중간해지므로, 처리 진행 상태를 기록해 재시작할 수 있게 만듭니다.
- 작업 큐 방식이면 FOR UPDATE SKIP LOCKED 로 잠긴 건을 건너뛰어 여러 프로세스가 동시에 처리할 수 있습니다.
- 온라인 업무와 시간대를 분리할 수 있으면 그것이 가장 단순한 해법입니다.
- TRUNCATE 는 DDL이라 테이블 배타 락을 걸고 되돌릴 수도 없습니다. 운영 중에는 쓰지 않습니다.
DECLARE
CURSOR c_target IS
SELECT order_id FROM orders
WHERE status_cd = 'READY' AND order_dt < SYSDATE - 30;
v_cnt NUMBER := 0;
BEGIN
FOR r IN c_target LOOP
UPDATE orders SET status_cd = 'EXPIRED' WHERE order_id = r.order_id;
v_cnt := v_cnt + 1;
IF MOD(v_cnt, 5000) = 0 THEN
COMMIT; -- 5000건마다 락을 풀어 줍니다
END IF;
END LOOP;
COMMIT;
END;
/커밋 주기가 너무 짧으면 오히려 느려지고, 너무 길면 락을 오래 잡습니다. 수천 건 단위가 일반적인 출발점이고 실제 환경에서 측정해 조정합니다. 중간 커밋을 하는 순간 전체 되돌리기는 포기하는 것이므로, 재실행해도 안전한 조건인지 먼저 확인합니다.
운영 중 DDL은 테이블을 멈춥니다
컬럼 추가, 인덱스 생성, 제약조건 변경은 모두 DDL이고 테이블에 배타 락을 요구합니다. 그 순간 해당 테이블을 쓰는 모든 작업이 막힙니다.
게다가 DDL은 락을 얻지 못하면 기본적으로 즉시 실패합니다. ORA-00054 가 나면 다른 세션이 그 테이블을 쓰고 있다는 뜻입니다.
-- 기본은 0초. 락을 못 잡으면 즉시 ORA-00054
ALTER SESSION SET ddl_lock_timeout = 30;
-- 인덱스 생성은 ONLINE 옵션으로 DML을 막지 않고 만들 수 있습니다
CREATE INDEX idx_orders_status ON orders (status_cd) ONLINE;ONLINE 옵션은 DML을 막지 않지만 부하는 그대로 발생하고 시간도 더 걸립니다. 작업 시간대를 잡는 것이 기본이고 ONLINE은 보조 수단입니다.
첫째 V$SESSION 에서 blocking_session 으로 원인 세션을 찾습니다. 둘째 그 세션이 INACTIVE 인지 ACTIVE 인지, 어떤 SQL을 실행했는지 확인합니다. 셋째 담당자를 확인해 스스로 커밋 또는 롤백하게 합니다. 넷째 연락이 안 되고 업무 영향이 크면 그때 세션을 끊습니다. 끊는 것이 첫 번째 선택지가 되면 안 됩니다.