WHEN OTHERS THEN NULL이 배치를 조용히 망가뜨리는 방식

PL/SQL 코드를 읽다 보면 이 패턴을 자주 만난다.

exception
when others then
null;
end;

에러가 나도 프로시저가 멈추지 않게 하려는 의도다. 배치가 중간에 죽는 것보다는 낫다는 판단인데, 실제로는 훨씬 더 나쁜 상황을 만든다.

무슨 일이 생기나

이 코드는 예외를 처리하는 게 아니라 없던 일로 만든다. 결과는 세 가지다.

첫째, 호출한 쪽이 성공으로 인식한다. 스케줄러 잡이라면 상태가 SUCCEEDED로 남는다. 모니터링에도 안 잡히고 알람도 안 울린다. 아무도 모르는 사이에 그날 처리가 비어 있다.

둘째, 어디서 죽었는지 알 수 없다. 에러 코드도 메시지도 줄 번호도 남지 않는다. 나중에 데이터가 이상하다는 걸 알아차려도 추적할 실마리가 없다.

셋째, 데이터가 중간 상태로 남는다. 이게 가장 위험하다.

begin
insert into stat_daily (work_dt, cnt) values (v_dt, v_cnt);
update tx_master set stat_yn = 'Y' where work_dt = v_dt;
commit;
exception
when others then
null;
end;

INSERT는 성공하고 UPDATE에서 에러가 났다고 하자. 예외가 삼켜지면서 COMMIT은 실행되지 않지만, 프로시저는 정상 종료한다. 호출한 쪽에서 나중에 COMMIT을 하면 집계는 쌓였는데 처리 플래그는 안 찍힌 상태가 확정된다. 다음 날 배치가 같은 데이터를 또 집계한다.

WHEN OTHERS 자체가 문제인가

아니다. 문제는 null;이다. WHEN OTHERS는 예상 못 한 예외를 붙잡는 마지막 그물로 필요하다. 다만 붙잡은 다음에 기록하고 다시 던져야 한다.

exception
when others then
log_error('PRC_DAILY_STAT', sqlcode, sqlerrm, dbms_utility.format_error_backtrace);
raise;
end;

여기서 핵심은 마지막 raise;다. 이게 있어야 호출한 쪽이 실패를 알고, 트랜잭션이 롤백되고, 스케줄러에 FAILED로 남는다. 로그는 남기되 흐름은 바꾸지 않는 것이 원칙이다.

로그가 같이 사라지는 함정

로그를 남기도록 고쳤는데도 로그 테이블이 비어 있는 경우가 있다. 예외가 발생하면 트랜잭션이 롤백되는데, 로그 INSERT도 같은 트랜잭션에 속해 있으면 같이 지워지기 때문이다.

로그는 본문과 분리된 트랜잭션에서 기록해야 한다. 자율 트랜잭션으로 선언한 별도 프로시저를 쓰는 것이 일반적인 방법이다.

create or replace procedure log_error(
p_prog varchar2, p_code number, p_msg varchar2, p_stack varchar2)
is
pragma autonomous_transaction;
begin
insert into job_err_log (log_dt, prog_nm, err_cd, err_msg, err_stack)
values (systimestamp, p_prog, p_code, p_msg, p_stack);
commit;
end;
/

자율 트랜잭션은 호출한 쪽과 독립적으로 커밋한다. 본문이 롤백돼도 로그는 남는다. 다만 이 프로시저 안에서는 반드시 COMMIT으로 끝내야 한다. 커밋 없이 끝나면 ORA-06519가 난다.

무엇을 남길 것인가

SQLERRM만 남기면 “ORA-01722: invalid number” 정도만 얻는다. 어느 줄에서 났는지를 알려면 백트레이스가 필요하다.

  • sqlcode — 오라클 에러 번호
  • sqlerrm — 에러 메시지
  • dbms_utility.format_error_backtrace — 예외가 발생한 프로그램과 줄 번호
  • dbms_utility.format_error_stack — 예외 스택 전체
  • 처리 중이던 키 값 — 어느 건에서 났는지를 알려면 이게 있어야 한다

백트레이스는 예외를 다시 던지기 전에 읽어야 원래 발생 지점이 남는다. 상위에서 다시 잡아 출력하면 발생 지점이 덮여 버린다.

정말로 무시해야 하는 예외라면

드물지만 무시가 맞는 경우가 있다. 이미 있는 행을 넣으려다 나는 중복 키 에러를 넘어가야 하는 상황 같은 것이다. 이때도 WHEN OTHERS를 쓰지 않는다. 무시할 예외만 정확히 지정한다.

exception
when dup_val_on_index then
null;
when others then
log_error('PRC_SYNC', sqlcode, sqlerrm, dbms_utility.format_error_backtrace);
raise;
end;

이러면 의도한 예외만 넘어가고 나머지는 전부 기록되고 전파된다. 예외 이름이 없는 오라클 에러는 EXCEPTION_INIT으로 이름을 붙여 쓸 수 있다.

declare
e_child_found exception;
pragma exception_init(e_child_found, -2292);
begin
null;
end;
/

정리

when others then null은 배치를 안 죽게 만드는 게 아니라 죽은 것을 안 보이게 만든다. 실패는 그대로 일어나고, 다만 아무도 모르는 상태로 데이터에 남는다. 보통 몇 주 뒤에 숫자가 안 맞는 형태로 발견되고, 그때는 어디서부터 틀렸는지 알 수 없다.

기존 코드에 이 패턴이 많다면 한 번에 걷어내기는 어렵다. 다만 실행 빈도가 높은 것과 데이터를 변경하는 것부터 순서대로 바꿔 가면 된다. 그리고 새로 짜는 코드에는 넣지 않으면 된다.

기준은 하나다. 예외를 잡았으면 기록하고 다시 던진다. 무시할 예외는 이름을 지정해서 무시한다. 그 둘 사이에 when others then null이 들어갈 자리는 없다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤