테이블과 스키마 이행
기존 exp, imp와 Data Pump로 테이블, 스키마를 옮기는 명령, Directory Object와 타깃 계정, 테이블스페이스 준비, REMAP 옵션, DB 링크와 MINUS로 이행 결과를 대조하는 방법
#핵심 요약
테이블 범위는 지정한 테이블을, 스키마 범위는 해당 소유자의 여러 객체를 옮기는 예제임
대상 사용자, 테이블스페이스, 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 방식)
exp <USERNAME>/<PASSWORD> tables=emp file=emp.dmp # 테이블 범위
exp system/<PASSWORD> owner=scott file=scott.dmp # 스키마 범위
scp scott.dmp oracle@<TARGET_HOST>:<WORK_DIR>/CREATE USER scott2 IDENTIFIED BY <PASSWORD>
DEFAULT TABLESPACE users QUOTA UNLIMITED ON users;
GRANT CONNECT, RESOURCE TO scott2;imp system/<PASSWORD> file=scott.dmp fromuser=scott touser=scott2결과 해석:
dump 방식은 클라이언트 쪽에서 동작해 서버 디렉터리가 필요 없고 명령이 단순해 소량을 빨리 옮길 때 쓰며, Data Pump는 서버 프로세스가 처리해 디렉터리를 먼저 만들어야 하지만 대용량을 훨씬 빨리 옮김
스키마 범위는 부모 테이블도 함께 오므로 테이블 범위에서 쓰던 constraints=n이 필요 없다고 수업 자료는 설명함
객체를 만들거나 컴파일할 권한이 모자라면 경고가 나고 일부 DDL이 빠질 수 있음
#Data Pump (pump 방식)
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;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>/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');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 전에 타깃 계정을 미리 준비함
대상 계정이 자동으로 생성되는 조건은 확인 불가
#스키마 이행 결과 대조
CREATE DATABASE LINK src_link CONNECT TO <USERNAME> IDENTIFIED BY <PASSWORD> USING '<SOURCE_HOST>:1521/<SERVICE_NAME>';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 | 계정 생성 정보가 빠지므로 타깃에 계정을 먼저 만들어야 함 |
-- 소스: 두 인스턴스 모두 <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개를 만들고 객체 수와 건수를 비교 기준으로 적어 둠
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>/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를 씀
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;결과 해석:
SELECT employees_seq.NEXTVAL FROM dual;
UPDATE employees SET job_id = 'SA_MAN' WHERE employee_id = 150;SELECT COUNT(*) FROM job_history;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 테이블스페이스로 하나 더 올려야 함
# <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_tsSELECT 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(*)로 확인하는 설명이 있음