오라클 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의 결정적 단점
- 외부 OS 명령 실행 불가 — PL/SQL 블록만 돌릴 수 있어서 백업 스크립트, 외부 파일 처리 등은 불가능
- INTERVAL이 모호하고 가독성 떨어짐 — 'SYSDATE + 1/24'가 매시간이라는 걸 한눈에 알기 어려움
- 실행 이력 추적 어려움 — 실패 사유, 실행 시간 같은 정보가 빈약
- 작업 간 의존성 표현 불가 — "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$_숫자 형태로 자동 생성됩니다.
실무에서 주의할 점
- JOB_QUEUE_PROCESSES 파라미터는 둘이 공유합니다. DBMS_JOB과 DBMS_SCHEDULER 작업의 동시 실행 수 합계가 이 값을 넘을 수 없습니다.
- DBMS_JOB의 트랜잭션 동작은 유지됩니다. 19c에서도 COMMIT을 안 하면 등록되지 않습니다 (이 부분만큼은 호환성이 살아있음).
- 업그레이드 시 기존 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;
/
마이그레이션 시 체크리스트
- 기존 작업의 정확한 실행 시각 확인 — dba_jobs.next_date, interval 분석
- 변환된 repeat_interval 검증 — dbms_scheduler.evaluate_calendar_string으로 다음 실행 시각 미리 확인
- 테스트 환경에서 먼저 실행 — 운영 환경 직접 변경은 금물
- 모니터링 추가 — 마이그레이션 후 첫 1주일은 dba_scheduler_job_run_details 매일 확인
- 기존 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이 많은 운영 환경이라면 한 번에 옮기지 말고, 작업 단위로 점진적으로 마이그레이션하는 것이 안전합니다.
질문이나 비슷한 마이그레이션 경험이 있다면 댓글로 공유해 주세요.
'DBA 실무 > Oracle(오라클)' 카테고리의 다른 글
| [오라클 실무] DBMS_SCHEDULER로 프로시저를 6시간 단위 실행하기 - 동적 인자 전달 완벽 예제 (0) | 2026.06.01 |
|---|---|
| [오라클 에러] ORA-28000 계정이 잠겼습니다 - 잠금 해제부터 재발 방지까지 (실무 DBA 정리) (0) | 2026.05.30 |
| [오라클 에러] ORA-01017 사용자명/비밀번호 무효 - 6가지 원인과 해결방법 (12c, 19c, 21c 차이까지) (0) | 2026.05.29 |
| [오라클 에러] ORA-12541 TNS 리스너가 없습니다 - 5가지 원인과 해결방법 (실무 DBA 정리) (1) | 2026.05.28 |
| 오라클(Oracle) 데이터 타입 (0) | 2024.08.12 |