데이터베이스 연구실.log

테이블과 스키마 이행

기존 exp, imp와 Data Pump로 테이블, 스키마를 옮기는 명령, Directory Object와 타깃 계정, 테이블스페이스 준비, REMAP 옵션, DB 링크와 MINUS로 이행 결과를 대조하는 방법

CLUSTER=NRAC Data PumpORA-31684expimpFROMUSERexpdpimpdpORA-31640TABLESSCHEMASOWNERREMAP_SCHEMAREMAP_TABLESPACE

#핵심 요약

테이블 범위는 지정한 테이블을, 스키마 범위는 해당 소유자의 여러 객체를 옮기는 예제임

대상 사용자, 테이블스페이스, Quota와 덤프 위치를 확인하고 이름 매핑을 정함

#개념과 동작 원리

작업 주요 옵션과 의미
기존 Export의 테이블 선택 TABLES로 테이블 지정, FILE로 파일 지정
기존 Export의 스키마 선택 OWNER로 소유자 지정
기존 Import의 소유자 변경 FROMUSER와 TOUSER로 소스, 대상 소유자 지정
Data Pump 테이블 선택 TABLES로 선택, DIRECTORY와 DUMPFILE로 서버 파일 지정
Data Pump 스키마 선택 SCHEMAS로 소유자 지정
스키마 변경 REMAP_SCHEMA의 실습 소유자와 타깃 소유자 지정
저장 위치 변경 REMAP_TABLESPACE의 왼쪽은 원래 객체가 실제 사용한 테이블스페이스
테이블 이름 변경 REMAP_TABLE로 소스, 타깃 이름 지정
작업 기록 LOGFILE로 Import 로그 지정

스키마 실습에서는 테이블, 인덱스, 뷰, 시퀀스, 시노님, 제약조건, 트리거, 저장 프로그램, 통계의 이전을 설명함
다른 스키마에 있는 부모 테이블과 참조 객체까지 모두 포함되는지는 별도 확인이 필요함

#필수 명령어 또는 SQL

#기존 Export/Import (dump 방식)

oracle 계정 셸 (소스)조회만 함
exp <USERNAME>/<PASSWORD> tables=emp file=emp.dmp           # 테이블 범위
exp system/<PASSWORD> owner=scott file=scott.dmp             # 스키마 범위
scp scott.dmp oracle@<TARGET_HOST>:<WORK_DIR>/
타깃 SQL*Plus (SYSDBA)객체 또는 설정을 변경함
CREATE USER scott2 IDENTIFIED BY <PASSWORD>
  DEFAULT TABLESPACE users QUOTA UNLIMITED ON users;
GRANT CONNECT, RESOURCE TO scott2;
oracle 계정 셸 (타깃)객체 또는 설정을 변경함
imp system/<PASSWORD> file=scott.dmp fromuser=scott touser=scott2

결과 해석:

dump 방식은 클라이언트 쪽에서 동작해 서버 디렉터리가 필요 없고 명령이 단순해 소량을 빨리 옮길 때 쓰며, Data Pump는 서버 프로세스가 처리해 디렉터리를 먼저 만들어야 하지만 대용량을 훨씬 빨리 옮김

스키마 범위는 부모 테이블도 함께 오므로 테이블 범위에서 쓰던 constraints=n이 필요 없다고 수업 자료는 설명함
객체를 만들거나 컴파일할 권한이 모자라면 경고가 나고 일부 DDL이 빠질 수 있음

#Data Pump (pump 방식)

소스 SQL*Plus (SYSDBA)객체 또는 설정을 변경함
CREATE DIRECTORY datapump_dir AS '<DUMP_DIR>';
SELECT directory_name, directory_path FROM dba_directories WHERE directory_name = 'DATAPUMP_DIR';
GRANT READ, WRITE ON DIRECTORY datapump_dir TO hr;
oracle 계정 셸 (소스)조회만 함
expdp <USERNAME>/<PASSWORD> directory=datapump_dir tables=employees dumpfile=emp_pump.dmp
expdp system/<PASSWORD> directory=datapump_dir schemas=scott dumpfile=scott_pump.dmp
ls -lh <DUMP_DIR>/          # 덤프와 로그 파일
scp emp_pump.dmp oracle@<TARGET_HOST>:<WORK_DIR>/
타깃 SQL*Plus (SYSDBA)객체 또는 설정을 변경함
CREATE DIRECTORY datapump_dir AS '<DUMP_DIR>';
CREATE TABLESPACE ts100 DATAFILE '<DATA_DIR>/ts100.dbf' SIZE 100m;
CREATE USER hr2 IDENTIFIED BY <PASSWORD>
  DEFAULT TABLESPACE ts100 QUOTA UNLIMITED ON ts100;
GRANT CONNECT, RESOURCE TO hr2;
GRANT READ, WRITE ON DIRECTORY datapump_dir TO hr2;

-- 원래 테이블스페이스 확인 (REMAP_TABLESPACE의 왼쪽 값)
SELECT table_name, tablespace_name, owner FROM dba_tables WHERE table_name IN ('EMP', 'DEPT');
oracle 계정 셸 (타깃)객체 또는 설정을 변경함
mv <WORK_DIR>/emp_pump.dmp <DUMP_DIR>/
impdp <USERNAME>/<PASSWORD> directory=datapump_dir dumpfile=emp_pump.dmp \
  remap_schema=hr:hr2 remap_tablespace=USERS:TS100 logfile=emp_imp.log

결과 해석:

scp로 <WORK_DIR>에 받았다면 Import 전에 타깃 Directory Object가 가리키는 경로로 덤프를 옮김

ORA-31640은 덤프 파일 경로나 파일 문제, ORA-39087은 Directory Object나 권한 문제로 수업 자료는 정리함

다른 스키마로 REMAP_SCHEMA 할 때 권한이 모자라면 SYSTEM 같은 관리 계정으로 실행함

Import 전에 타깃 계정을 미리 준비함
대상 계정이 자동으로 생성되는 조건은 확인 불가

#스키마 이행 결과 대조

타깃 SQL*Plus (이행된 계정)객체 또는 설정을 변경함
CREATE DATABASE LINK src_link CONNECT TO <USERNAME> IDENTIFIED BY <PASSWORD> USING '<SOURCE_HOST>:1521/<SERVICE_NAME>';
타깃 SQL*Plus (이행된 계정)조회만 함
SELECT table_name FROM user_tables
MINUS
SELECT table_name FROM user_tables@src_link;

SELECT table_name FROM user_tables@src_link
MINUS
SELECT table_name FROM user_tables;

결과 해석: 두 방향 모두 행이 없으면 테이블 이름이 같음
같은 방식으로 user_indexes, user_constraints, user_sequences, user_synonyms, user_views를 대조하고, 행 수는 통계 NUM_ROWS 대신 COUNT(*)로 셈
DB 링크를 만들려면 CREATE DATABASE LINK 권한이 필요함

#RAC PDB 스키마를 싱글 DB로 (CLUSTER=N)

실습: RAC CDB의 <PDB_NAME>에 있는 HR 스키마를 2번 노드에서 Export하고, 1번 노드에 따로 만든 싱글 non-CDB <DB_NAME>으로 Import한 뒤 객체 수, 건수, INVALID를 비교함

항목 내용
덤프가 생기는 곳 클라이언트 PC가 아니라 Data Pump 워커(DM00, DW00)가 도는 DB 서버의 DIRECTORY 경로
RAC의 워커 배치 CLUSTER=Y(기본)이면 워커가 다른 인스턴스에도 뜰 수 있어, 로컬 디스크 경로면 파일이 노드별로 흩어지거나 ORA-39001, ORA-31693이 날 수 있음
로컬 경로 사용 노드 리스너로 원하는 인스턴스에 직접 붙고 CLUSTER=N으로 워커를 그 인스턴스에 고정
공유 경로 ACFS 같은 공유 경로면 CLUSTER=Y와 PARALLEL을 쓸 수 있음
SYSTEM으로 스키마 Export 계정 정의(SCHEMA_EXPORT/USER), 시스템 권한, 기본 롤, 쿼터, 테이블, 인덱스, 제약조건, 시퀀스, 뷰, 프로시저, 트리거, 통계가 들어감
테이블스페이스와 롤 자체, 퍼블릭 시노님 같은 스키마 밖 객체는 따로 확인
HR로 Export 계정 생성 정보가 빠지므로 타깃에 계정을 먼저 만들어야 함
SQL*Plus (소스 2번 노드 CDB$ROOT·타깃 <DB_NAME> SYSDBA)조회만 함
-- 소스: 두 인스턴스 모두 <PDB_NAME>이 READ WRITE인지
SELECT inst_id, name, open_mode FROM gv$pdbs WHERE name = '<PDB_NAME>';
-- 타깃: non-CDB, 캐릭터셋(소스와 같아야 함), USERS 유무, HR이 아직 없는지
SELECT name, cdb FROM v$database;
SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
SELECT tablespace_name FROM dba_tablespaces;
SELECT username FROM dba_users WHERE username = 'HR';

결과 해석:

실습 기록: 소스 <PDB_NAME> 두 인스턴스 모두 READ WRITE, 타깃 <DB_NAME>, CDB NO, 양쪽 AL32UTF8, 타깃 HR 없음

캐릭터셋이 다르면 한글 데이터와 컬럼 길이에서 ORA-12899가 날 수 있음

소스 HR에는 테이블 7개(FK 순환 참조인 departments.manager_id는 데이터를 넣은 뒤 FK 추가), 인덱스 2개, 시퀀스 3개, 뷰, 프로시저, 트리거 각 1개를 만들고 객체 수와 건수를 비교 기준으로 적어 둠

oracle 계정 셸 (소스 2번 노드)객체 또는 설정을 변경함
mkdir -p <DUMP_DIR>
sqlplus system/<PASSWORD>@<RAC2_HOST>:1521/<SERVICE_NAME>
# SQL> SELECT instance_name, host_name FROM v$instance;   → 2번 인스턴스인지 확인
# SQL> CREATE OR REPLACE DIRECTORY dp_dir AS '<DUMP_DIR>';
expdp system/<PASSWORD>@<RAC2_HOST>:1521/<SERVICE_NAME> \
  schemas=hr directory=dp_dir dumpfile=hr_export.dmp logfile=exp_hr_export.log cluster=n
ls -l <DUMP_DIR>/
scp <DUMP_DIR>/hr_export.dmp <RAC1_HOST>:<DUMP_DIR>/
oracle 계정 셸 (타깃 1번 노드, <DB_NAME> 환경)객체 또는 설정을 변경함
echo $ORACLE_SID      # 타깃 <TARGET_INSTANCE>인지 확인, RAC 인스턴스면 소스 쪽에 들어감
sqlplus / as sysdba
# SQL> CREATE OR REPLACE DIRECTORY dp_dir AS '<DUMP_DIR>';
impdp system/<PASSWORD> schemas=hr directory=dp_dir dumpfile=hr_export.dmp logfile=imp_hr.log

결과 해석:

실습 기록: Export 로그에 SCHEMA_EXPORT/USER가 있고 EMPLOYEES 8행, DEPARTMENTS 5행 등이 나갔으며, 덤프는 2번 노드 로컬에만 생기고 1번 노드에는 없음

Import 로그에서 SCHEMA_EXPORT/USER로 <DB_NAME>에 HR이 만들어지고, 데이터 적재 뒤 FK(REF_CONSTRAINT, 순환 FK 포함)와 트리거가 만들어지는 순서를 봄

노드 사이 복사는 RAC 설치 때 만든 oracle 계정 SSH 등가성을 씀

ORA-31684: Object type USER:"HR" already exists는 타깃에 같은 계정이 있는 경우, ORA-00959는 타깃에 USERS가 없는 경우이며 이름이 다르면 REMAP_TABLESPACE를 씀

SQL*Plus (타깃 <DB_NAME>, HR)조회만 함
SELECT sys_context('userenv', 'db_name') db FROM dual;
SELECT object_type, COUNT(*) FROM user_objects GROUP BY object_type ORDER BY 1;
SELECT COUNT(*) FROM user_constraints;
SELECT object_name, object_type, status FROM user_objects WHERE status = 'INVALID';
SELECT first_name, last_name, department_name, city FROM emp_details_view WHERE employee_id = 150;

결과 해석:

SQL*Plus (타깃 <DB_NAME>, HR)객체 또는 설정을 변경함
SELECT employees_seq.NEXTVAL FROM dual;
UPDATE employees SET job_id = 'SA_MAN' WHERE employee_id = 150;
SQL*Plus (타깃 <DB_NAME>, HR)조회만 함
SELECT COUNT(*) FROM job_history;
SQL*Plus (타깃 <DB_NAME>, HR)객체 또는 설정을 변경함
ROLLBACK;

결과 해석:

실습 기록: 소스와 같은 INDEX 10(PK 7 + UK 1 + 일반 2), PROCEDURE 1, SEQUENCE 3, TABLE 7, TRIGGER 1, VIEW 1, 테이블 건수 4, 4, 4, 5, 6, 8, 2, INVALID 없음

예상값: 시퀀스는 소스에서 쓰지 않아 시작값 207, UPDATE 뒤 트리거가 프로시저로 이력을 한 건 넣어 job_history 3건, 롤백 뒤 2건

HR 비밀번호는 소스의 암호 해시가 그대로 넘어와 소스와 같은 비밀번호로 접속됨

INVALID가 있으면 SYSDBA에서 EXEC dbms_utility.compile_schema('HR', compile_all => false)로 다시 컴파일하고 다시 조회함

시퀀스는 Export 시점 값이라 운영 중에 소스 값이 더 늘 수 있으므로 컷오버 때 다시 맞춤

재연습할 때는 <DB_NAME>에서 DROP USER hr CASCADE를 하되, 반드시 접속 DB가 <DB_NAME>인지 확인하고 실행함

확인 문제: 2번 노드가 점검 중이라 1번 노드에서 작업하고, <DB_NAME>의 기존 HR은 덮어쓰지 않도록 hr_test 계정과 hr_ts 테이블스페이스로 하나 더 올려야 함

oracle 계정 셸 (1번 노드)객체 또는 설정을 변경함
# <DB_NAME>에서 먼저: CREATE TABLESPACE hr_ts DATAFILE '<DATA_DIR>/hr_ts01.dbf' SIZE 100M;
expdp system/<PASSWORD>@<RAC1_HOST>:1521/<SERVICE_NAME> \
  schemas=hr directory=dp_dir dumpfile=hr_export_node1.dmp logfile=exp_hr_export_node1.log cluster=n
# <DB_NAME> 환경으로 바꾼 뒤
impdp system/<PASSWORD> schemas=hr directory=dp_dir dumpfile=hr_export_node1.dmp logfile=imp_hr_test.log \
  remap_schema=hr:hr_test remap_tablespace=users:hr_ts
SQL*Plus (타깃 <DB_NAME> SYSDBA)조회만 함
SELECT owner, tablespace_name, COUNT(*) FROM dba_tables
 WHERE owner IN ('HR', 'HR_TEST') GROUP BY owner, tablespace_name;

결과 해석:

1번 노드 리스너로 1번 인스턴스에 직접 붙어야 덤프가 1번 노드 로컬에 생기며, SCAN으로 붙으면 연결 인스턴스를 고정할 수 없음

덤프가 생기는 1번 노드 <DUMP_DIR>를 <DB_NAME>의 DIRECTORY도 그대로 가리키므로 이번에는 scp가 필요 없음

예상값: HR / USERS / 7, HR_TEST / HR_TS / 7

수업 자료는 hr_test가 원래 HR 비밀번호로 접속된다고 예상하지만, Oracle 19c Data Pump Import 문서의 REMAP_SCHEMA 설명은 새로 만드는 대상 사용자를 사용할 수 없는 비밀번호(unusable password)로 만든다고 함
Import 뒤 계정 상태와 접속을 확인하고, 접속되지 않으면 비밀번호를 정한 뒤 다시 확인함

#사용 조건과 주의사항

Import 때 대상 계정이 자동으로 만들어지는지와 암호 보존 여부는 확인 불가

타깃 덤프는 해당 DIRECTORY가 가리키는 실제 경로 안에 두고 OS 접근 권한과 DB READ/WRITE 권한을 확인하는 흐름임

스키마 이행 후에는 객체 이름을 양방향 MINUS로 대조하고 실제 행수는 통계 NUM_ROWS 대신 COUNT(*)로 확인하는 설명이 있음

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기