DBA 실무/Oracle(오라클)

[오라클] DBMS_JOB과 DBMS_SCHEDULER 차이점 - 언제 뭘 써야 할까? (19c 변환 동작까지)

isony 2026. 6. 1. 16:29
반응형

오라클 DBMS_JOB과 DBMS_SCHEDULER 차이점 - 언제 뭘 써야 할까? (19c 변환 동작까지)

테스트 환경: Oracle 11g / 12c / 19c / 21c

오라클에서 작업을 자동화할 때 가장 많이 검색되는 질문 중 하나가 "DBMS_JOB과 DBMS_SCHEDULER, 둘 다 있는데 뭐가 다른가?" 입니다.

결론부터 말씀드리면, 2026년 현재 시점에서 새로 만드는 작업은 100% DBMS_SCHEDULER를 써야 합니다. DBMS_JOB은 12cR2부터 deprecated되었고, 19c부터는 사용해도 내부적으로 DBMS_SCHEDULER로 변환되어 동작합니다.

그렇다면 왜 아직도 운영 환경에 DBMS_JOB이 살아있고, 두 개의 차이를 알아둬야 하는 걸까요? 이번 글에서 둘의 핵심 차이, 19c부터 달라진 동작, 그리고 기존 DBMS_JOB을 DBMS_SCHEDULER로 옮기는 실무 방법까지 정리했습니다.

 

한 줄 요약

구분 DBMS_JOB DBMS_SCHEDULER

등장 시점 Oracle 7 이전부터 Oracle 10g부터
현재 상태 12cR2부터 deprecated 권장 표준
19c 이상 동작 내부적으로 DBMS_SCHEDULER로 변환 그대로 사용
새 개발 권장 여부 ❌ 사용 금지 ✅ 권장

새 코드는 무조건 DBMS_SCHEDULER. 이게 끝입니다. 다만 운영 중인 DBMS_JOB을 이해하고 마이그레이션하려면 두 패키지의 차이를 알아야 합니다.

 

 

핵심 차이점 비교

항목 DBMS_JOB DBMS_SCHEDULER

작업 종류 PL/SQL 블록만 PL/SQL, 저장 프로시저, OS 실행파일, 외부 스크립트, 체인(Chain)
외부 OS 명령 실행 ❌ 불가 ✅ 가능 (EXECUTABLE 타입)
트랜잭션 동작 트랜잭션의 일부 (COMMIT 해야 등록됨) 별도 트랜잭션 (즉시 커밋)
스케줄 표현 DATE 산술식 (SYSDATE+1) 캘린더 표현식 (FREQ=DAILY;BYHOUR=2)
의존성/체인 ❌ 없음 ✅ 작업 간 의존 관계 정의 가능
리소스 관리 ❌ 없음 ✅ Resource Manager 연동
작업 클래스 ❌ 없음 ✅ Job Class로 그룹 관리
모니터링 뷰 DBA_JOBS, DBA_JOBS_RUNNING DBA_SCHEDULER_JOBS, DBA_SCHEDULER_JOB_RUN_DETAILS 등 풍부
오류 처리 빈약 상세한 실행 이력 및 실패 사유 기록

이 표만 봐도 DBMS_SCHEDULER가 모든 면에서 우위라는 게 명확합니다. 단 한 가지, 트랜잭션 동작은 상황에 따라 DBMS_JOB이 유리한 경우가 있는데, 이건 뒤에서 다룹니다.

 

DBMS_JOB - 옛날 방식

기본 사용법

DECLARE
    v_job NUMBER;
BEGIN
    DBMS_JOB.SUBMIT(
        job       => v_job,
        what      => 'BEGIN my_procedure; END;',
        next_date => SYSDATE,
        interval  => 'SYSDATE + 1/24'   -- 1시간마다
    );
    COMMIT;   -- ★ 이게 없으면 등록 안 됨
END;
/

DBMS_JOB의 결정적 단점

  1. 외부 OS 명령 실행 불가 — PL/SQL 블록만 돌릴 수 있어서 백업 스크립트, 외부 파일 처리 등은 불가능
  2. INTERVAL이 모호하고 가독성 떨어짐 — 'SYSDATE + 1/24'가 매시간이라는 걸 한눈에 알기 어려움
  3. 실행 이력 추적 어려움 — 실패 사유, 실행 시간 같은 정보가 빈약
  4. 작업 간 의존성 표현 불가 — "A 끝나면 B 실행" 같은 체인 구성 안 됨

그래도 살아남는 이유: 트랜잭션 일부 동작 ★

DBMS_JOB은 트랜잭션의 일부로 작업을 등록합니다. 즉, COMMIT을 해야만 작업이 실제로 큐에 들어갑니다. 이게 의외로 유용한 케이스가 있습니다.

시나리오: 트리거에서 이메일 발송 작업을 등록하는데, 트리거를 발동시킨 트랜잭션이 롤백되면 이메일도 발송되면 안 되는 경우.

DBMS_JOB을 쓰면 트랜잭션이 롤백될 때 등록된 작업도 같이 사라집니다. 반면 DBMS_SCHEDULER는 등록 즉시 커밋되어 별도 트랜잭션이 되므로, 트리거 안에서 호출하면 본 트랜잭션이 롤백되어도 이메일은 발송됩니다.

19c부터 DBMS_SCHEDULER도 CREATE_JOBS(복수형) 프로시저에 commit_semantics => 'TRANSACTIONAL' 옵션이 추가되어 이 동작을 구현할 수 있지만, 단일 작업 등록에는 적용되지 않아 여전히 일부 시나리오에서는 DBMS_JOB의 호환성이 필요한 상황이 남아 있습니다.

조회

SELECT job, what, last_date, next_date, interval, broken
FROM dba_jobs;

 

 

DBMS_SCHEDULER - 표준 방식

기본 사용법

BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'MY_DAILY_JOB',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN my_procedure; END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=DAILY;BYHOUR=2;BYMINUTE=30',  -- 매일 02:30
        enabled         => TRUE,
        comments        => '일일 통계 갱신 작업'
    );
END;
/

COMMIT이 필요 없습니다. CREATE_JOB 자체가 즉시 커밋됩니다.

repeat_interval 표현식 - 캘린더 구문

이게 DBMS_SCHEDULER의 강점입니다. 자연어에 가까운 표현이 가능합니다.

원하는 동작 repeat_interval 표현

매일 02:30 FREQ=DAILY;BYHOUR=2;BYMINUTE=30
매주 월~금 09:00 FREQ=WEEKLY;BYDAY=MON,TUE,WED,THU,FRI;BYHOUR=9
매월 1일 00:00 FREQ=MONTHLY;BYMONTHDAY=1;BYHOUR=0
매월 마지막 평일 FREQ=MONTHLY;BYDAY=-1MON,-1TUE,-1WED,-1THU,-1FRI
5분마다 FREQ=MINUTELY;INTERVAL=5
매년 1월 1일 FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1

DBMS_JOB의 'TRUNC(SYSDATE+1)+2.5/24' 보다 압도적으로 가독성이 좋습니다.

다양한 작업 유형

-- 1) PL/SQL 블록
job_type => 'PLSQL_BLOCK'

-- 2) 저장 프로시저
job_type => 'STORED_PROCEDURE'
job_action => 'SCHEMA.PACKAGE.PROCEDURE'

-- 3) OS 실행 파일 (★ DBMS_JOB은 불가능했던 영역)
job_type => 'EXECUTABLE'
job_action => '/u01/app/scripts/backup.sh'

-- 4) 외부 작업 (원격 서버)
job_type => 'EXTERNAL_SCRIPT'

OS 스크립트 실행은 운영 자동화의 핵심입니다. 백업, 로그 정리, 외부 시스템 연동 등이 가능해집니다.

조회

-- 작업 목록
SELECT job_name, job_type, repeat_interval, state, enabled
FROM dba_scheduler_jobs
WHERE owner = 'SCOTT';

-- 실행 이력 (실패 사유 포함)
SELECT job_name, status, run_duration, error#, errors,
       actual_start_date
FROM dba_scheduler_job_run_details
WHERE owner = 'SCOTT'
ORDER BY actual_start_date DESC;

실패 시 error#(에러 코드)과 errors(상세 메시지)까지 자동으로 기록됩니다. DBMS_JOB과는 비교할 수 없는 운영 편의성입니다.

 

★ 19c부터 달라진 동작 (이걸 모르면 헷갈립니다)

대부분의 한국어 블로그가 이 부분을 놓치고 있는데, 운영 DB를 19c로 업그레이드했다면 반드시 알아야 합니다.

무엇이 변했나

19c부터 DBMS_JOB.SUBMIT을 호출해도 내부적으로 DBMS_SCHEDULER 작업으로 생성됩니다. 백워드 호환성을 위해 인터페이스는 살려두되, 실제 실행 엔진은 통합한 것입니다.

확인 방법

19c 환경에서 DBMS_JOB으로 작업을 만들어 보면 양쪽 뷰에 다 보입니다.

-- 1) DBMS_JOB으로 작업 생성
DECLARE v_job NUMBER;
BEGIN
    DBMS_JOB.SUBMIT(v_job, 'BEGIN NULL; END;', SYSDATE, 'SYSDATE+1');
    COMMIT;
END;
/

-- 2) 두 뷰 모두에서 조회됨
SELECT job, what FROM user_jobs;
SELECT job_name, job_action FROM user_scheduler_jobs;

user_scheduler_jobs에서 보면 작업 이름이 DBMS_JOB$_숫자 형태로 자동 생성됩니다.

실무에서 주의할 점

  1. JOB_QUEUE_PROCESSES 파라미터는 둘이 공유합니다. DBMS_JOB과 DBMS_SCHEDULER 작업의 동시 실행 수 합계가 이 값을 넘을 수 없습니다.
  2. DBMS_JOB의 트랜잭션 동작은 유지됩니다. 19c에서도 COMMIT을 안 하면 등록되지 않습니다 (이 부분만큼은 호환성이 살아있음).
  3. 업그레이드 시 기존 DBMS_JOB이 자동 변환됩니다. 변환 불가능한 작업이 있으면 JOB_TABLE_INTEGRITY 경고가 뜨므로 업그레이드 전 점검이 필요합니다.

21c 이후는?

21c에서도 DBMS_JOB은 여전히 사용 가능하지만, 언젠가는 완전히 제거(desupport)될 예정입니다. 신규 개발은 무조건 DBMS_SCHEDULER로 가야 하는 이유입니다.

 

어떤 걸 써야 할까? 실무 가이드

상황별 결정 트리입니다.

신규 작업 개발

무조건 DBMS_SCHEDULER. 예외 없음. 단순한 PL/SQL 호출이라도 DBMS_SCHEDULER로 시작하세요. 나중에 외부 스크립트 호출이 추가되거나 복잡한 스케줄이 필요해질 때 돌아오는 비용이 큽니다.

기존 DBMS_JOB이 운영 중

점진적 마이그레이션 권장. 19c에서 자동 변환되어 동작은 하지만, 다음 시점에 옮기는 게 좋습니다.

  • 해당 작업의 로직을 수정할 일이 생겼을 때
  • DB 업그레이드 작업과 함께
  • 작업 실패 추적이 필요해질 때

트리거 안에서 비동기 작업 등록

여전히 DBMS_JOB이 유리한 거의 유일한 경우. 트랜잭션과 함께 롤백되어야 하는 작업이면 DBMS_JOB을 유지하거나, DBMS_SCHEDULER의 트랜잭셔널 옵션을 활용하세요.

 

DBMS_JOB → DBMS_SCHEDULER 마이그레이션 예시

기존 DBMS_JOB 작업이 다음과 같다면:

-- 기존 (DBMS_JOB) - 매일 새벽 2시 30분 실행
DECLARE v_job NUMBER;
BEGIN
    DBMS_JOB.SUBMIT(
        job       => v_job,
        what      => 'BEGIN stats_proc; END;',
        next_date => TRUNC(SYSDATE+1) + 2.5/24,
        interval  => 'TRUNC(SYSDATE+1) + 2.5/24'
    );
    COMMIT;
END;
/

DBMS_SCHEDULER로 옮기면:

-- 새 방식 (DBMS_SCHEDULER)
BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'STATS_DAILY_JOB',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN stats_proc; END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=DAILY;BYHOUR=2;BYMINUTE=30',
        enabled         => TRUE,
        comments        => '일일 통계 작업 (DBMS_JOB에서 마이그레이션)'
    );
END;
/

마이그레이션 시 체크리스트

  1. 기존 작업의 정확한 실행 시각 확인 — dba_jobs.next_date, interval 분석
  2. 변환된 repeat_interval 검증 — dbms_scheduler.evaluate_calendar_string으로 다음 실행 시각 미리 확인
  3. 테스트 환경에서 먼저 실행 — 운영 환경 직접 변경은 금물
  4. 모니터링 추가 — 마이그레이션 후 첫 1주일은 dba_scheduler_job_run_details 매일 확인
  5. 기존 DBMS_JOB 제거 — 새 작업이 정상 동작 확인 후 DBMS_JOB.REMOVE(job_id)

 

자주 발생하는 트러블슈팅

작업이 등록은 됐는데 실행이 안 됨

-- 1) JOB_QUEUE_PROCESSES 확인
SHOW PARAMETER job_queue_processes;
-- 0이면 모든 작업이 중지됨. 권장값: 1000 또는 기본값

작업이 disabled 상태로 등록됨

DBMS_SCHEDULER는 기본값이 enabled => FALSE입니다. 명시적으로 TRUE를 주거나, 생성 후 ENABLE하세요.

DBMS_SCHEDULER.ENABLE('MY_JOB');

RAC 환경에서 특정 인스턴스에서만 실행하고 싶을 때

DBMS_SCHEDULER.SET_ATTRIBUTE(
    name      => 'MY_JOB',
    attribute => 'INSTANCE_ID',
    value     => 1
);

실행 시각이 예상과 다를 때

타임존 문제일 가능성이 높습니다. 19c부터 DBMS_SCHEDULER가 세션 타임존을 사용하는 동작 차이로 인해 업그레이드 후 실행 시각이 변경되는 사례가 보고되었습니다. start_date에 명시적 타임존을 지정하세요.

start_date => TO_TIMESTAMP_TZ('2026-01-01 02:30:00 Asia/Seoul',
                              'YYYY-MM-DD HH24:MI:SS TZR')

 

마무리

DBMS_JOB과 DBMS_SCHEDULER는 같은 기능을 하는 두 가지 패키지가 아니라, 세대가 다른 도구입니다. 2026년 현재 시점에서 새 작업은 무조건 DBMS_SCHEDULER이며, DBMS_JOB은 호환성을 위한 레거시 인터페이스로만 봐야 합니다.

특히 19c부터 DBMS_JOB이 내부적으로 DBMS_SCHEDULER로 변환되어 동작한다는 사실은 운영 DB 업그레이드 시 반드시 알아야 할 내용입니다. 두 시스템이 자원을 공유하기 때문에 트러블슈팅 시 양쪽 뷰를 모두 확인하는 습관을 들이세요.

기존 DBMS_JOB이 많은 운영 환경이라면 한 번에 옮기지 말고, 작업 단위로 점진적으로 마이그레이션하는 것이 안전합니다.

질문이나 비슷한 마이그레이션 경험이 있다면 댓글로 공유해 주세요.

 

반응형