728x90

학습목표

  1. VMware에 Rocky Linux 8.10 설치
  2. Oracle 19c 설치
  3. 데이터베이스 실행 및 사용자, 테이블 생성, 데이터 생성
  4. 테이블 생성, 데이터 생성(PL/SQL) SQL Script

 

1. VMware에 Rocky Linux 8.10 설치 Rocky Linux 설치

  • Rocky Linux : Rocky Linux는 RHEL(Red Hat Enterprise Linux)과 100% 호환되는 무료 오픈소스 리눅스 배포판, CentOS의 대체재로 탄생
  • https://dl.rockylinux.org/pub/rocky/8/isos/x86_64/ 들어가서 iso 다운로드
    • ISO 파일은 CD나 DVD의 이미지 파일로, 국제 표준화 기구(ISO)가 제정한 광학 디스크의 압축 파일. "이미지 파일”이라고도 불림. 마치 디스크를 사진처럼 찍어서 파일로 만든 느낌

  • VMware 세팅

 

네트워크 유형 설명 외부 네트워크 연결 가능 여부
Bridged (VMnet0) 게스트 OS가 실제 물리 네트워크와 직접 연결됨 (공유기에서 IP 할당) ✅ 가능
NAT (VMnet8) VMware가 가상 게이트웨이 역할을 하며, 게스트 OS가 NAT을 통해 인터넷 연결 가능 ✅ 가능
Host-Only (VMnet1) 게스트 OS와 호스트 OS 간에만 통신 가능, 외부 네트워크 연결 불가 ❌ 불가능
Custom 사용자가 직접 네트워크 환경을 설정 가능 설정에 따라 다름

 

 

 

2. Oracle 19c 설치

  • oracle 19c rpm 다운로드(yum 안돼서 직접 ISO 파일 다운 or SSH로 로컬 PC에서 VMware로 파일전송)

 

  • oracle-database-preinstall-19c 설치
sudo dnf install -y oracle-database-preinstall-19c
# sudo : superuser do (관리자 권한으로 실행)
# dnf : Dandified YUM, RPM 기반 리눅스 배포판을 위한 패키지 관리도구
# install -y dnf가 설치를 시작하기 전에 물어보면 -y를 넣으면 자동으로 yes
  • Oracle 19c가 동작하는 데 필요한 라이브러리 및 유틸리티 패키지들을 자동으로 설치(glibc, binutils, gcc, libaio, make 등)
  • Oracle이 원활하게 실행되도록 공유 메모리, 파일 핸들 수, 네트워크 설정 등을 자동으로 변경(설정 파일: /etc/sysctl.conf)
    • kernel.shmmax = 4398046511104 # 공유 메모리 최대 크기
    • kernel.shmall = 1073741824 # 공유 메모리 페이지 수
    • fs.file-max = 6815744 # 최대 파일 핸들 개수

 

  • 오라클 설치 전
mkdir -p /u01/app/oracle # 오라클 데이터베이스가 설치될 기본 디렉토리
chown -R oracle:oinstall /u01/app/oracle # 소유자를 oracle로 변경
chmod -R 775 /u01/app/oracle # 적절한 권한을 부여

 

  • 오라클 사용자로 전환 후 설치 작업 ( su - oracle )
  • 사용자 프로파일 수정 vi /home/oracle/.bash_profile

  • ORACLE_BASE : 오라클 기본 디렉토리
  • ORACLE_HOME : 오라클 프로그램이 설치된 디렉토리
  • ORACLE_SID : 데이터베이스 인스턴스 이름
  • PATH : 오라클 실행 파일을 PATH에 추가
  • NLS_LANG : 오라클 메시지 시스템 설정이 한국어라 깨짐 방지로 설정 필요

 

  • 이후 바뀐 설정 적용 명령어 : source /home/oracle/.bash_profile

 

  • which dbca 명령어로 dbca 확인 후 실행
    • dbca (Database Configuration Assistant) : 데이터베이스를 자동으로 생성하는 유틸리티
dbca -silent -createDatabase \
    -gdbName orcl \
    -sid orcl \
    -templateName "General_Purpose.dbc" \
    -characterSet AL32UTF8 \
    -memoryPercentage 40 \
    -emConfiguration NONE \
    -sysPassword ora951753 \
    -systemPassword ora951753 \
    -createAsContainerDatabase true

 

옵션 설명
-silent 무인 설치 (GUI 없이 자동 실행)
-createDatabase 새 데이터베이스 생성
-gdbName orcl 전역 데이터베이스 이름 설정
-sid orcl 인스턴스 이름 (SID) 설정
-templateName "General_Purpose.dbc" 템플릿 지정 (OLTP용)
-characterSet AL32UTF8 문자셋 UTF-8 설정 (다국어 지원)
-memoryPercentage 40 서버 메모리의 40%를 Oracle에 할당
-emConfiguration NONE Enterprise Manager 설정 안 함
-sysPassword ora951753 SYS 관리자 비밀번호 설정
-systemPassword ora951753 SYSTEM 관리자 비밀번호 설정
-createAsContainerDatabase true CDB(컨테이너 DB) 활성화

 

  • General_Purpose.dbc : database options, storage attributes, init.ora 설정값이나 파라미터를 참고할 수 있는 기본 템플릿 파일
728x90

3. 데이터베이스 실행 및 사용자, 테이블 생성, 데이터 생성

  • 리스너 실행
netca # (Network Configuration Assistant)는 Oracle Listener를 설정하는 유틸리티
lsnrctl start # 리스너 start
lsnrctl status # 리스너 상태 확인

 

-- sqlplus / as sysdba 로 sqlplus 접속 후
SELECT NAME, OPEN_MODE FROM V$DATABASE; -- 로 실행중인 DATABASE 확인

CREATE USER C##ERP IDENTIFIED BY 1234 DEFAULT TABLESPACE USERS;
GRANT CONNECT, RESOURCE TO C##ERP;
ALTER USER C##ERP QUOTA UNLIMITED ON USERS;
  • 오라클 12c부터는 공통계정앞에 c##을 붙이도록 네이밍 규칙이 바뀜. C## 가 붙는 이유는 12c 버전부터 등장하는 CDB, PDB 개념 때문
  • nano 로 테이블, 데이터생성을 위한 SQL 스크립트 파일 생성 (글 맨 아래 해당 스크립트 파일별로 적어놓음)
  • sqlplus를 해당 폴더에서 실행 후 @파일명 으로 스크립트 실행

EXEC DBMS_STATS.GATHER_SCHEMA_STATS('C##ERP'); -- 해당 유저의 테이블 통계 업데이트

-- 생성된 테이블 row 수 확인
SELECT table_name, num_rows FROM user_tables WHERE num_rows IS NOT NULL;

 

 

4. 테이블 생성, 데이터 생성(PL/SQL) SQL Script

  • 테이블 생성 스크립트
/***************************************
****************CUST_COM****************
****************************************/

 DROP TABLE "CUST_COM";

CREATE TABLE "CUST_COM" (
	"CUST_ID"	VARCHAR2(10)		NOT NULL,
	"CUST_GRP"	CHAR(4)		NOT NULL,
	"NAME"	VARCHAR2(30)		NOT NULL,
	"CNTR_CODE"	CHAR(4)		NULL,
	"CITY_CODE"	CHAR(4)		NULL,
	"POST_CODE"	CHAR(5)		NULL,
	"ADDR1"	VARCHAR2(100)		NULL,
	"ADDR2"	VARCHAR2(100)		NULL,
	"REPR_NAME"	VARCHAR(30)		NULL,
	"BIZ_TYPE"	VARCHAR(30)		NULL,
	"IDST_TYPE"	VARCHAR(20)		NULL,
	"REMK"	VARCHAR2(200)		NULL,
	"STAT"	CHAR(1)		NULL,
	"DEL_FLAG"	CHAR(1)		NULL,
	"CREA_BY"	VARCHAR(20)		NULL,
	"CREA_DATE"	DATE		NULL,
	"UPDT_BY"	VARCHAR(20)		NULL,
	"UPDT_DATE"	DATE		NULL
);

COMMENT ON COLUMN "CUST_COM"."CUST_ID" IS '고객아이디';

COMMENT ON COLUMN "CUST_COM"."CUST_GRP" IS '고객그룹';

COMMENT ON COLUMN "CUST_COM"."NAME" IS '이름';

COMMENT ON COLUMN "CUST_COM"."CNTR_CODE" IS '국가코드';

COMMENT ON COLUMN "CUST_COM"."CITY_CODE" IS '도시코드';

COMMENT ON COLUMN "CUST_COM"."POST_CODE" IS '우편번호';

COMMENT ON COLUMN "CUST_COM"."ADDR1" IS '주소1';

COMMENT ON COLUMN "CUST_COM"."ADDR2" IS '주소2';

COMMENT ON COLUMN "CUST_COM"."REPR_NAME" IS '대표자이름';

COMMENT ON COLUMN "CUST_COM"."BIZ_TYPE" IS '사업유형';

COMMENT ON COLUMN "CUST_COM"."IDST_TYPE" IS '산업유형';

COMMENT ON COLUMN "CUST_COM"."REMK" IS '비고';

COMMENT ON COLUMN "CUST_COM"."STAT" IS '상태';

COMMENT ON COLUMN "CUST_COM"."DEL_FLAG" IS '삭제플래그';

COMMENT ON COLUMN "CUST_COM"."CREA_BY" IS '생성자';

COMMENT ON COLUMN "CUST_COM"."CREA_DATE" IS '생성일자';

COMMENT ON COLUMN "CUST_COM"."UPDT_BY" IS '수정자';

COMMENT ON COLUMN "CUST_COM"."UPDT_DATE" IS '수정일자';

ALTER TABLE "CUST_COM" ADD CONSTRAINT "PK_CUST_COM" PRIMARY KEY (
	"CUST_ID"
);

/***************************************
****************MATR_COM****************
****************************************/

 DROP TABLE "MATR_COM";

CREATE TABLE "MATR_COM" (
	"MATR_CODE"	VARCHAR2(10)		NOT NULL,
	"MATR_TYPE"	CHAR(4)		NOT NULL,
	"NAME"	VARCHAR2(30)		NOT NULL,
	"DESC"	VARCHAR2(100)		NULL,
	"PRDT_LINE"	CHAR(3)		NULL,
	"BASE_UOM"	VARCHAR(5)		NULL,
	"MATR_HIER"	VARCHAR2(30)		NULL,
	"MATR_HIER_LV1"	CHAR(3)		NULL,
	"MATR_HIER_LV2"	CHAR(3)		NULL,
	"MATR_HIER_LV3"	CHAR(3)		NULL,
	"MATR_HIER_LV4"	CHAR(3)		NULL,
	"MATR_HIER_LV5"	CHAR(3)		NULL,
	"STAT"	CHAR(1)		NULL,
	"DEL_FLAG"	CHAR(1)		NULL,
	"CREA_BY"	VARCHAR(20)		NULL,
	"CREA_DATE"	DATE		NULL,
	"UPDT_BY"	VARCHAR(20)		NULL,
	"UPDT_DATE"	DATE		NULL
);

COMMENT ON COLUMN "MATR_COM"."MATR_CODE" IS '자재코드';

COMMENT ON COLUMN "MATR_COM"."MATR_TYPE" IS '자재유형';

COMMENT ON COLUMN "MATR_COM"."NAME" IS '이름';

COMMENT ON COLUMN "MATR_COM"."DESC" IS '설명';

COMMENT ON COLUMN "MATR_COM"."PRDT_LINE" IS '제품라인';

COMMENT ON COLUMN "MATR_COM"."BASE_UOM" IS '기본측정단위';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER" IS '자재계층구조';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER_LV1" IS '자재계층구조레벨1';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER_LV2" IS '자재계층구조레벨2';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER_LV3" IS '자재계층구조레벨3';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER_LV4" IS '자재계층구조레벨4';

COMMENT ON COLUMN "MATR_COM"."MATR_HIER_LV5" IS '자재계층구조레벨5';

COMMENT ON COLUMN "MATR_COM"."STAT" IS '상태';

COMMENT ON COLUMN "MATR_COM"."DEL_FLAG" IS '삭제플래그';

COMMENT ON COLUMN "MATR_COM"."CREA_BY" IS '생성자';

COMMENT ON COLUMN "MATR_COM"."CREA_DATE" IS '생성일자';

COMMENT ON COLUMN "MATR_COM"."UPDT_BY" IS '수정자';

COMMENT ON COLUMN "MATR_COM"."UPDT_DATE" IS '수정일자';

ALTER TABLE "MATR_COM" ADD CONSTRAINT "PK_MATR_COM" PRIMARY KEY (
	"MATR_CODE"
);

/***************************************
**************SALE_ORD_HEAD*************
****************************************/

 DROP TABLE "SALE_ORD_HEAD";

CREATE TABLE "SALE_ORD_HEAD" (
	"SALE_ORD_NO"	NUMBER(10)		NOT NULL,
	"SOLD_TO_PTY"	VARCHAR2(10)		NOT NULL,
	"ORD_DATE"	DATE		NOT NULL,
	"ORD_TYPE"	CHAR(4)		NOT NULL,
	"SALE_DEPT"	CHAR(3)		NULL,
	"SALE_OFIS"	CHAR(3)		NULL,
	"SALE_GRP"	CHAR(3)		NULL,
	"DIST_CHAN"	CHAR(2)		NOT NULL,
	"ORD_RESN"	CHAR(3)		NULL,
	"PRIC_DATE"	DATE		NULL,
	"CURR"	CHAR(3)		NOT NULL,
	"PRC_STAT"	CHAR(1)		NULL,
	"RJEC_RESN"	CHAR(2)		NULL,
	"DEL_FLAG"	CHAR(1)		NULL,
	"CREA_BY"	VARCHAR(20)		NULL,
	"CREA_DATE"	DATE		NULL,
	"UPDT_BY"	VARCHAR(20)		NULL,
	"UPDT_DATE"	DATE		NULL
);

COMMENT ON COLUMN "SALE_ORD_HEAD"."SALE_ORD_NO" IS '영업문서번호';

COMMENT ON COLUMN "SALE_ORD_HEAD"."SOLD_TO_PTY" IS '판매처(고객아이디)';

COMMENT ON COLUMN "SALE_ORD_HEAD"."ORD_DATE" IS '주문일자';

COMMENT ON COLUMN "SALE_ORD_HEAD"."ORD_TYPE" IS '주문유형';

COMMENT ON COLUMN "SALE_ORD_HEAD"."SALE_DEPT" IS '영업부서';

COMMENT ON COLUMN "SALE_ORD_HEAD"."SALE_OFIS" IS '영업오피스';

COMMENT ON COLUMN "SALE_ORD_HEAD"."SALE_GRP" IS '영업그룹';

COMMENT ON COLUMN "SALE_ORD_HEAD"."DIST_CHAN" IS '유통채널';

COMMENT ON COLUMN "SALE_ORD_HEAD"."ORD_RESN" IS '주문사유';

COMMENT ON COLUMN "SALE_ORD_HEAD"."PRIC_DATE" IS '가결결정일';

COMMENT ON COLUMN "SALE_ORD_HEAD"."CURR" IS '통화';

COMMENT ON COLUMN "SALE_ORD_HEAD"."PRC_STAT" IS '처리상태';

COMMENT ON COLUMN "SALE_ORD_HEAD"."RJEC_RESN" IS '거부사유';

COMMENT ON COLUMN "SALE_ORD_HEAD"."DEL_FLAG" IS '삭제플래그';

COMMENT ON COLUMN "SALE_ORD_HEAD"."CREA_BY" IS '생성자';

COMMENT ON COLUMN "SALE_ORD_HEAD"."CREA_DATE" IS '생성일자';

COMMENT ON COLUMN "SALE_ORD_HEAD"."UPDT_BY" IS '수정자';

COMMENT ON COLUMN "SALE_ORD_HEAD"."UPDT_DATE" IS '수정일자';

ALTER TABLE "SALE_ORD_HEAD" ADD CONSTRAINT "PK_SALE_ORD_HEAD" PRIMARY KEY (
	"SALE_ORD_NO"
);

ALTER TABLE "SALE_ORD_HEAD" ADD CONSTRAINT "FK_CUST_COM_TO_SALE_ORD_HEAD_1" FOREIGN KEY (
	"SOLD_TO_PTY"
)
REFERENCES "CUST_COM" (
	"CUST_ID"
);


/***************************************
**************SALE_ORD_ITEM*************
****************************************/

DROP TABLE "SALE_ORD_ITEM";

CREATE TABLE "SALE_ORD_ITEM" (
	"ITEM_SEQ"	NUMBER(5)		NOT NULL,
	"SALE_ORD_NO"	NUMBER(10)		NOT NULL,
	"SHIP_TO_PTY"	VARCHAR2(10)		NOT NULL,
	"MATR_CODE"	VARCHAR2(10)		NOT NULL,
	"ITEM_NAME"	VARCHAR2(30)		NULL,
	"PRDT_LINE"	CHAR(3)		NULL,
	"ORD_QTY"	NUMBER(18,6)		NULL,
	"SALE_UOM"	VARCHAR(5)		NULL,
	"NET_VAL"	NUMBER(18,6)		NULL,
	"CURR"	CHAR(3)		NULL,
	"GROS_VAL"	NUMBER(18,6)		NULL,
	"TAX_VAL"	NUMBER(18,6)		NULL,
	"DELV_REQ_DATE"	DATE		NULL,
	"RJEC_RESN"	CHAR(2)		NULL,
	"PRC_STAT"	CHAR(1)		NULL,
	"CREA_BY"	VARCHAR(20)		NULL,
	"CREA_DATE"	DATE		NULL,
	"UPDT_BY"	VARCHAR(20)		NULL,
	"UPDT_DATE"	DATE		NULL
);

COMMENT ON COLUMN "SALE_ORD_ITEM"."ITEM_SEQ" IS '아이템순번';

COMMENT ON COLUMN "SALE_ORD_ITEM"."SALE_ORD_NO" IS '영업문서번호';

COMMENT ON COLUMN "SALE_ORD_ITEM"."SHIP_TO_PTY" IS '납품처(고객아이디)';

COMMENT ON COLUMN "SALE_ORD_ITEM"."MATR_CODE" IS '자재코드';

COMMENT ON COLUMN "SALE_ORD_ITEM"."ITEM_NAME" IS '아이템이름';

COMMENT ON COLUMN "SALE_ORD_ITEM"."PRDT_LINE" IS '제품라인';

COMMENT ON COLUMN "SALE_ORD_ITEM"."ORD_QTY" IS '주문수량';

COMMENT ON COLUMN "SALE_ORD_ITEM"."SALE_UOM" IS '영업측정단위';

COMMENT ON COLUMN "SALE_ORD_ITEM"."NET_VAL" IS '순가치';

COMMENT ON COLUMN "SALE_ORD_ITEM"."CURR" IS '통화';

COMMENT ON COLUMN "SALE_ORD_ITEM"."GROS_VAL" IS '총가치';

COMMENT ON COLUMN "SALE_ORD_ITEM"."TAX_VAL" IS '세금가치';

COMMENT ON COLUMN "SALE_ORD_ITEM"."DELV_REQ_DATE" IS '납품요청일';

COMMENT ON COLUMN "SALE_ORD_ITEM"."RJEC_RESN" IS '거부사유';

COMMENT ON COLUMN "SALE_ORD_ITEM"."PRC_STAT" IS '처리상태';

COMMENT ON COLUMN "SALE_ORD_ITEM"."CREA_BY" IS '생성자';

COMMENT ON COLUMN "SALE_ORD_ITEM"."CREA_DATE" IS '생성일자';

COMMENT ON COLUMN "SALE_ORD_ITEM"."UPDT_BY" IS '수정자';

COMMENT ON COLUMN "SALE_ORD_ITEM"."UPDT_DATE" IS '수정일자';

ALTER TABLE "SALE_ORD_ITEM" ADD CONSTRAINT "PK_SALE_ORD_ITEM" PRIMARY KEY (
	"ITEM_SEQ",
	"SALE_ORD_NO"
);

ALTER TABLE "SALE_ORD_ITEM" ADD CONSTRAINT "FK_SALE_ORD_HEAD_TO_SALE_ORD_ITEM_1" FOREIGN KEY (
	"SALE_ORD_NO"
)
REFERENCES "SALE_ORD_HEAD" (
	"SALE_ORD_NO"
);

ALTER TABLE "SALE_ORD_ITEM" ADD CONSTRAINT "FK_CUST_COM_TO_SALE_ORD_ITEM_1" FOREIGN KEY (
	"SHIP_TO_PTY"
)
REFERENCES "CUST_COM" (
	"CUST_ID"
);

ALTER TABLE "SALE_ORD_ITEM" ADD CONSTRAINT "FK_MATR_COM_TO_SALE_ORD_ITEM_1" FOREIGN KEY (
	"MATR_CODE"
)
REFERENCES "MATR_COM" (
	"MATR_CODE"
);

 

  • 고객 공통 정보 테이블(CUST_COM) 데이터 생성
DECLARE
    TYPE CompanyArray IS TABLE OF VARCHAR2(100);
    company_names CompanyArray := CompanyArray(
        'Alpha Corporation', 'Beta Tech', 'Gamma Solutions', 'Delta Enterprises', 'Epsilon Holdings',
        'Zeta Innovations', 'Theta Solutions', 'Iota Technologies', 'Kappa Enterprises', 'Lambda Group',
        'Mu Systems', 'Nu Consulting', 'Xi Manufacturing', 'Omicron Inc.', 'Pi Enterprises',
        'Rho Financials', 'Sigma Tech', 'Tau Solutions', 'Upsilon Logistics', 'Phi Innovations',
        'Chi Holdings', 'Psi Retail', 'Omega Consulting', 'AlphaPlus Tech', 'BetaMax Solutions',
        'GammaPrime Inc.', 'DeltaX Enterprises', 'EpsilonSoft', 'ZetaPro Consulting', 'ThetaAI',
        'Omega Retail', 'Lambda Innovations', 'Kappa Systems', 'Mu Technologies', 'Nu Financials',
        'Xi Logistics', 'Omicron Consulting', 'Pi Tech', 'Rho Manufacturing', 'Sigma Retail',
        'Tau AI', 'Upsilon Innovations', 'Phi Systems', 'Chi Enterprises', 'Psi Holdings',
        'Omega AI', 'AlphaEdge Solutions', 'BetaCore Tech', 'GammaFusion', 'DeltaCloud'
    );
    v_cust_id    VARCHAR2(10);
    v_cust_grp   CHAR(4);
    v_cntr_code  CHAR(4);
    v_city_code  CHAR(4);
    v_post_code  CHAR(5);
    v_addr1      VARCHAR2(100);
    v_addr2      VARCHAR2(100);
    v_repr_name  VARCHAR2(30);
    v_biz_type   VARCHAR2(30);
    v_idst_type  VARCHAR2(20);
    v_remk       VARCHAR2(200);
    v_stat       CHAR(1);
    v_del_flag   CHAR(1);
    v_crea_by    VARCHAR2(20);
    v_crea_date  DATE;
    v_updt_by    VARCHAR2(20);
    v_updt_date  DATE;
BEGIN
    FOR i IN 1..company_names.COUNT LOOP
        -- 랜덤한 CUST_ID 생성 (CUST_00001 ~ CUST_99999)
        v_cust_id := LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 9999999999)), 10, '0');
        
        -- 랜덤한 그룹 코드 (A001 ~ A005)
        v_cust_grp := 'A00' || TRUNC(DBMS_RANDOM.VALUE(1, 4));

        -- CNTR_CODE (랜덤 국가코드)
        v_cntr_code := CASE TRUNC(DBMS_RANDOM.VALUE(1, 6))
            WHEN 1 THEN 'KR' WHEN 2 THEN 'US' WHEN 3 THEN 'JP'
            WHEN 4 THEN 'CN' WHEN 5 THEN 'SG' ELSE 'DE' END;
        
        -- CITY_CODE (랜덤 도시코드)
        v_city_code := CASE v_cntr_code
            WHEN 'KR' THEN 'SEO' WHEN 'US' THEN 'NY' WHEN 'JP' THEN 'TKY'
            WHEN 'CN' THEN 'SH' WHEN 'SG' THEN 'SIN' ELSE 'BER' END;

        -- 랜덤한 우편번호 (10000 ~ 99999)
        v_post_code := LPAD(TRUNC(DBMS_RANDOM.VALUE(10000, 99999)), 5, '0');

        -- 주소 랜덤 생성
        v_addr1 := 'Street ' || TRUNC(DBMS_RANDOM.VALUE(1, 500));
        v_addr2 := 'Building ' || TRUNC(DBMS_RANDOM.VALUE(1, 100));

        -- 대표자명 및 업종 정보 랜덤
        v_repr_name := 'CEO_' || DBMS_RANDOM.STRING('U', 3);
        v_biz_type := 'Biz_' || DBMS_RANDOM.STRING('U', 4);
        v_idst_type := 'Industry_' || TRUNC(DBMS_RANDOM.VALUE(1, 10));

        -- 비고 (REMK) 및 상태 (STAT), 삭제 플래그 (DEL_FLAG)
        v_remk := 'Generated by PL/SQL';

        -- STAT (A: Active, I: Inactive, D: Deleted 랜덤)
        v_stat := CASE TRUNC(DBMS_RANDOM.VALUE(1, 4))
            WHEN 1 THEN 'A' ELSE 'I' END;

        v_del_flag := '';

        -- 생성자, 생성 날짜, 업데이트 정보
        v_crea_by := 'System';
        v_crea_date := SYSDATE - TRUNC(DBMS_RANDOM.VALUE(1, 365)); -- 최근 1년 내 생성
        v_updt_by := 'System';
        v_updt_date := v_crea_date + TRUNC(DBMS_RANDOM.VALUE(0, 30)); -- 생성일 이후 랜덤 업데이트

        -- 데이터 삽입
        INSERT INTO CUST_COM (
            CUST_ID, CUST_GRP, NAME, CNTR_CODE, CITY_CODE, POST_CODE,
            ADDR1, ADDR2, REPR_NAME, BIZ_TYPE, IDST_TYPE, REMK, STAT,
            DEL_FLAG, CREA_BY, CREA_DATE, UPDT_BY, UPDT_DATE
        ) VALUES (
            v_cust_id, v_cust_grp, company_names(i), v_cntr_code, v_city_code, v_post_code,
            v_addr1, v_addr2, v_repr_name, v_biz_type, v_idst_type, v_remk, v_stat,
            v_del_flag, v_crea_by, v_crea_date, v_updt_by, v_updt_date
        );

    END LOOP;

    -- 커밋
    COMMIT;

    DBMS_OUTPUT.PUT_LINE('✅ 50개 데이터가 CUST_COM 테이블에 삽입되었습니다.');
END;
/

  • 자재 공통 정보 테이블(MATR_COM) 데이터 생성
DELETE MATR_COM;
INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1001', 'FNGD', 'Steel Plate', 'Heavy-duty steel plate', 'PLT', 'KG', 'STL', '001', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1002', 'WINP', 'Copper Wire', 'High conductivity copper wire', 'WR', 'M', 'COP', '001', '001', '002', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1003', 'WINP', 'Aluminum Sheet', 'Aluminum alloy sheet', 'ALM', 'M2', 'ALM', '001', '002', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1004', 'FNGD', 'Plastic Pellet', 'Polyethylene pellets for molding', 'PLT', 'KG', 'PLA', '002', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1005', 'FNGD', 'Rubber Mold', 'High-density rubber mold', 'RUB', 'KG', 'RUB', '002', '002', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1006', 'FNGD', 'Iron Rod', 'Solid iron rod for construction', 'CON', 'M', 'IRN', '003', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1007', 'FNGD', 'Cement Bag', 'Portland cement for construction', 'CON', 'KG', 'CEM', '003', '001', '002', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1008', 'WINP', 'Brick', 'Red clay brick for wall construction', 'CON', 'PC', 'BRK', '003', '002', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1009', 'FNGD', 'LED Panel', 'Energy-efficient LED panel', 'ELE', 'EA', 'LED', '004', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1010', 'FNGD', 'Power Supply', '12V power supply for LED lights', 'ELE', 'EA', 'PSU', '004', '002', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1011', 'FNGD', 'Fiber Optic Cable', 'High-speed fiber optic cable', 'TEL', 'M', 'FOC', '005', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1012', 'FNGD', 'Wireless Router', 'High-speed wireless router', 'TEL', 'EA', 'WLR', '005', '001', '002', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1013', 'FNGD', 'Bicycle Frame', 'Aluminum bicycle frame', 'BIK', 'EA', 'BIC', '006', '001', '001', 'A', 'N', 'admin', SYSDATE);

INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1014', 'FNGD', 'Bike Tire', 'Durable rubber tire for bicycles', 'BIK', 'EA', 'TIR', '006', '002', '001', 'A', 'N', 'admin', SYSDATE);

-- 서비스 자재 2: 소프트웨어 라이선스
INSERT INTO MATR_COM (MATR_CODE, MATR_TYPE, NAME, "DESC", PRDT_LINE, BASE_UOM, MATR_HIER, MATR_HIER_LV1, MATR_HIER_LV2, MATR_HIER_LV3, STAT, DEL_FLAG, CREA_BY, CREA_DATE)
VALUES ('1015', 'SRVC', 'Software License', 'Annual software license for office suite', 'SVC', 'EA', 'SWL', '008', '002', '001', 'A', 'N', 'admin', SYSDATE);

COMMIT;

UPDATE MATR_COM SET MATR_HIER = MATR_HIER_LV1 || MATR_HIER_LV2 || MATR_HIER_LV1;

COMMIT;
  • 주문 헤더 테이블(SALE_ORD_HEAD) 데이터 생성
DECLARE
    v_sale_ord_no NUMBER := 1;
    v_sold_to_pty VARCHAR2(10);
    v_ord_date DATE := TO_DATE('2025-01-01', 'YYYY-MM-DD');
    v_ord_type CHAR(4);
    v_sale_dept CHAR(3);
    v_sale_ofis CHAR(3);
    v_dist_chan CHAR(2);
    v_ord_resn CHAR(3);
    v_curr CHAR(3);
    v_prc_stat CHAR(1);
    v_del_flag CHAR(1);
    
    TYPE cust_id_table IS TABLE OF VARCHAR2(10);
    v_cust_ids cust_id_table;
    
    v_daily_count NUMBER;
    v_total_count NUMBER := 0;
    
    CURSOR c_cust IS SELECT CUST_ID FROM CUST_COM;
    
BEGIN
    -- CUST_COM 테이블에서 모든 CUST_ID를 가져와서 배열에 저장
    OPEN c_cust;
    FETCH c_cust BULK COLLECT INTO v_cust_ids;
    CLOSE c_cust;

    -- 2025년 1월 1일부터 12월 31일까지 반복
    WHILE v_ord_date <= TO_DATE('2025-12-31', 'YYYY-MM-DD') LOOP
        -- 하루에 100~200건 생성 (랜덤)
        v_daily_count := TRUNC(DBMS_RANDOM.VALUE(150, 300));

        FOR i IN 1..v_daily_count LOOP
            EXIT WHEN v_total_count >= 100000; -- 10만 건에 근접하면 종료

            -- SOLD_TO_PTY 랜덤 선택
            v_sold_to_pty := v_cust_ids(TRUNC(DBMS_RANDOM.VALUE(1, v_cust_ids.COUNT + 1)));

            -- ORD_TYPE 설정 (비율 9:0.2:0.2:0.2:0.2:0.2)
            v_ord_type := CASE 
                WHEN DBMS_RANDOM.VALUE < 0.9 THEN 'NORM'
                WHEN DBMS_RANDOM.VALUE < 0.92 THEN 'FREE'
                WHEN DBMS_RANDOM.VALUE < 0.94 THEN 'SRVC'
                WHEN DBMS_RANDOM.VALUE < 0.96 THEN 'RETN'
                WHEN DBMS_RANDOM.VALUE < 0.98 THEN 'AJCR'
                ELSE 'AJDR'
            END;

            -- SALE_DEPT 설정
            v_sale_dept := CASE TRUNC(DBMS_RANDOM.VALUE(1, 5))
                WHEN 1 THEN 'A01'
                WHEN 2 THEN 'A02'
                WHEN 3 THEN 'B01'
                ELSE 'B02'
            END;

            -- SALE_OFIS 설정
            v_sale_ofis := CASE v_sale_dept
                WHEN 'A01' THEN CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN '10A' ELSE '11A' END
                WHEN 'A02' THEN CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN '20A' ELSE '21A' END
                WHEN 'B01' THEN CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN '10B' ELSE '11B' END
                ELSE CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN '20B' ELSE '21B' END
            END;

            -- DIST_CHAN 설정
            v_dist_chan := CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN 'LO' ELSE 'EX' END;

            -- ORD_RESN 설정
            v_ord_resn := NULL;
            IF v_ord_type = 'AJCR' THEN
                v_ord_resn := CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN 'SAL' ELSE 'SDC' END;
            ELSIF v_ord_type = 'FREE' THEN
                v_ord_resn := CASE WHEN DBMS_RANDOM.VALUE < 0.5 THEN 'SMP' ELSE 'QAN' END;
            ELSIF v_ord_type = 'RETN' THEN
                IF DBMS_RANDOM.VALUE < 0.5 THEN
                    v_ord_resn := 'DAM';
                END IF;
            END IF;

            -- CURR 설정
            v_curr := CASE WHEN v_dist_chan = 'LO' THEN 'KRW' ELSE 'USD' END;

            IF v_ord_date < TO_DATE('2025-12-01', 'YYYY-MM-DD') THEN
                v_prc_stat := CASE 
                    WHEN DBMS_RANDOM.VALUE < 0.98 THEN 'D'
                    ELSE 'E'
                END;
            ELSE 
                v_prc_stat := CASE 
                    WHEN DBMS_RANDOM.VALUE < 0.2 THEN 'A'
                    WHEN DBMS_RANDOM.VALUE < 0.2 THEN 'B'
                    WHEN DBMS_RANDOM.VALUE < 0.2 THEN 'C'
                    WHEN DBMS_RANDOM.VALUE < 0.3 THEN 'D'
                    WHEN DBMS_RANDOM.VALUE < 0.1 THEN 'E'
                    ELSE 'E'
                END;
            END IF;

            v_del_flag := NULL;
            IF v_prc_stat = 'E' AND DBMS_RANDOM.VALUE < 0.5 THEN
                v_del_flag := 'Y';
            END IF;

            -- 데이터 삽입
            INSERT INTO SALE_ORD_HEAD (
                SALE_ORD_NO, SOLD_TO_PTY, ORD_DATE, ORD_TYPE, SALE_DEPT, SALE_OFIS, 
                DIST_CHAN, ORD_RESN, CURR, PRIC_DATE, CREA_DATE, PRC_STAT, DEL_FLAG
            ) VALUES (
                v_sale_ord_no, v_sold_to_pty, v_ord_date, v_ord_type, v_sale_dept, v_sale_ofis, 
                v_dist_chan, v_ord_resn, v_curr, v_ord_date, v_ord_date, v_prc_stat, v_del_flag
            );

            -- SALE_ORD_NO 증가
            v_sale_ord_no := v_sale_ord_no + 1;
            v_total_count := v_total_count + 1;
            

            -- 1,000건마다 커밋
            IF MOD(v_total_count, 1000) = 0 THEN
                COMMIT;
            END IF;
        END LOOP;

        -- ORD_DATE 하루 증가
        v_ord_date := v_ord_date + 1;
    END LOOP;

    -- 마지막 커밋
    COMMIT;
    
END;
/

  • 주문 아이템 정보 테이블(SALE_ORD_ITEM) 데이터 생성
DECLARE
    TYPE matr_table IS TABLE OF MATR_COM%ROWTYPE;
    v_matr_list matr_table;
    
    TYPE sale_ord_table IS TABLE OF SALE_ORD_HEAD%ROWTYPE;
    v_sale_orders sale_ord_table;

    v_item_seq NUMBER;
    v_ship_to_pty VARCHAR2(10);
    v_matr_code VARCHAR2(10);
    v_item_name VARCHAR2(30);
    v_prdt_line CHAR(3);
    v_ord_qty NUMBER(18,6);
    v_sale_uom VARCHAR2(5);
    v_net_val NUMBER(18,6);
    v_gros_val NUMBER(18,6);
    v_tax_val NUMBER(18,6);
    v_delv_req_date DATE;
    
    v_total_count NUMBER := 0;
    
    CURSOR c_sale IS SELECT * FROM SALE_ORD_HEAD;
    CURSOR c_matr IS SELECT * FROM MATR_COM;
    
BEGIN
    -- MATR_COM 데이터를 가져와서 메모리에 저장
    OPEN c_matr;
    FETCH c_matr BULK COLLECT INTO v_matr_list;
    CLOSE c_matr;
    
    -- SALE_ORD_HEAD 데이터를 가져와서 메모리에 저장
    OPEN c_sale;
    FETCH c_sale BULK COLLECT INTO v_sale_orders;
    CLOSE c_sale;

    -- SALE_ORD_HEAD를 순회하면서 SALE_ORD_ITEM 데이터 삽입
    FOR i IN 1..v_sale_orders.COUNT LOOP
        -- SALE_ORD_HEAD 정보 가져오기
        v_ship_to_pty := v_sale_orders(i).SOLD_TO_PTY;

        -- 랜덤한 아이템 개수 (1~5개)
        FOR j IN 1..TRUNC(DBMS_RANDOM.VALUE(1,6)) LOOP
            v_item_seq := j;

            -- MATR_COM에서 랜덤한 데이터 선택
            IF v_sale_orders(i).ORD_TYPE = 'SRVC' THEN
                -- SRVC일 경우 PRDT_LINE이 'SVC'인 데이터 하나 포함
                SELECT MATR_CODE, NAME, PRDT_LINE, BASE_UOM
                INTO v_matr_code, v_item_name, v_prdt_line, v_sale_uom
                FROM MATR_COM
                WHERE PRDT_LINE = 'SVC'
                AND ROWNUM = 1;
            ELSE
                -- 일반적인 경우 랜덤한 제품 선택
                v_matr_code := v_matr_list(TRUNC(DBMS_RANDOM.VALUE(1, v_matr_list.COUNT + 1))).MATR_CODE;
                v_item_name := v_matr_list(TRUNC(DBMS_RANDOM.VALUE(1, v_matr_list.COUNT + 1))).NAME;
                v_prdt_line := v_matr_list(TRUNC(DBMS_RANDOM.VALUE(1, v_matr_list.COUNT + 1))).PRDT_LINE;
                v_sale_uom := v_matr_list(TRUNC(DBMS_RANDOM.VALUE(1, v_matr_list.COUNT + 1))).BASE_UOM;
            END IF;

            -- ORD_QTY 설정
            IF v_prdt_line = 'SVC' THEN
                v_ord_qty := 1;
            ELSE
                v_ord_qty := TRUNC(DBMS_RANDOM.VALUE(100, 1001));
            END IF;

            -- NET_VAL 설정
            IF v_sale_orders(i).ORD_TYPE = 'FREE' THEN
                v_net_val := 0;
            ELSIF v_sale_orders(i).ORD_TYPE = 'RETN' THEN
                v_net_val := -1 * v_ord_qty * TRUNC(DBMS_RANDOM.VALUE(10, 100));
            ELSE
                v_net_val := v_ord_qty * TRUNC(DBMS_RANDOM.VALUE(10, 100));
            END IF;

            -- GROS_VAL & TAX_VAL 설정
            v_gros_val := TRUNC(v_net_val * 1.1);
            v_tax_val := v_gros_val - v_net_val;

            -- DELV_REQ_DATE 설정 (8:1:1 비율)
            v_delv_req_date := v_sale_orders(i).ORD_DATE + CASE 
                WHEN DBMS_RANDOM.VALUE < 0.8 THEN 0 
                WHEN DBMS_RANDOM.VALUE < 0.9 THEN 1 
                ELSE 2 
            END;

            -- 데이터 삽입
            INSERT INTO SALE_ORD_ITEM (
                ITEM_SEQ, SALE_ORD_NO, SHIP_TO_PTY, MATR_CODE, ITEM_NAME, PRDT_LINE, ORD_QTY, 
                SALE_UOM, NET_VAL, CURR, GROS_VAL, TAX_VAL, DELV_REQ_DATE, 
                RJEC_RESN, PRC_STAT, CREA_DATE
            ) VALUES (
                v_item_seq, v_sale_orders(i).SALE_ORD_NO, v_ship_to_pty, v_matr_code, v_item_name, v_prdt_line, v_ord_qty,
                v_sale_uom, v_net_val, 
                CASE WHEN v_sale_orders(i).DIST_CHAN = 'LO' THEN 'KRW' ELSE 'USD' END,
                v_gros_val, v_tax_val, v_delv_req_date, 
                v_sale_orders(i).RJEC_RESN, v_sale_orders(i).PRC_STAT, v_sale_orders(i).CREA_DATE
            );

            -- 1,000건마다 COMMIT
            v_total_count := v_total_count + 1;
            IF MOD(v_total_count, 1000) = 0 THEN
                COMMIT;
            END IF;
        END LOOP;
    END LOOP;

    -- 마지막 COMMIT
    COMMIT;
    
END;
/

 

728x90
728x90

백업과 복구

  •  백업
    • 온라인 백업 : 데이터베이스 운영 상황에서 백업 => BEGIN BACKUP과 END BACKUP 사이에 변경이 일어난 블록을 Redo 로그에 기록하여 불완전 복구 방지
    • 콜드 백업 : 인스턴스 정지 후 백업
  • 복구
    • 장애(인스턴스) 복구 : 장애 발생 시 자동으로 실행되는 복구
    • 미디어 복구 : 사용자가 명시적으로 실행
    • 완전복구 : 최신 데이터까지 복구
    • 불완전 복구 : 특정 시점까지 복구
    • 데이터베이스/테이블스페이스/데이터파일/블록의 복구
  • 기본적인 복구 흐름
    1. 데이터베이스 손상 확인
    2. 재작업할 수 있도록 현재 상태 백업
    3. 필요한 데이터 파일과 아카이브 리두 로그 파일 restore
    4. 복구 실행

 

백그라운드 프로세스의 동작과 역할

  • DBWR(DBW) : 이미 변경된 데이터를 캐시에서 디스크로 기록
  • LGWR : commit 시 redo 로그를 기록
  • PMON : 서버 프로세스가 비정상 종료 시 메모리나 프로세스 정리 => 세션 및 프로세스 정리, 내부 락 해제 등
    • PMON이 인스턴스의 정보를 정기적으로 리스너에게 전달
  • SMON : 공간 청소 - 테이블 스페이스 빈 공간 합치기, 필요 이상의 undo 세그먼트 정리, 임시 세그먼트 정리 등
  • ARCH : redo 로그 파일을 아카이브하여 아카이브 로그 파일로 보관
  • RECO : 분산 트랜잭션 해결
  • CKPT : 체크포인트 작업 시 DBWR에게 작업 신호를 보내고, 가장 최신의 컨트롤 파일과 데이터 파일의 헤더에 체크포인트 기록

 

 

728x90
728x90

트랜잭션이 지켜야 할 원칙 ACID

  • Atomicity 원자성
    • 트랜잭션에 포함되는 데이터 변경은 'All or Nothing' 이어야 한다.
    • 즉, 트랜잭션은 더 이상 분할할 수 없는 데이터 변경의 최소 단위라는 의미이다.
  • Consistency 일관성
    • 트랜잭션에 의해 데이터 간 일관성이 어긋나서는 안된다.
    • 예를 들어, Customer 테이블의 데이터가 변경되었는데 Customer 테이블의 인덱스 데이터가 변경되지 않은 경우가 없어야 한다는 것이다.
  • Isolation 고립성
    • 트랜잭션은 다른 트랜잭션을 의식할 필요가 없어야 한다.
    • 즉, 트랜잭션은 고립되어 있어 단독 실행이든 다른 트랜잭션과 동시 실행이든 결과는 같아야 한다.
  • Durability 지속성
    • Commit한 트랜잭션은 장애가 발생하더라도 데이터는 반드시 복구되어야 한다.

 

 

Redo, Undo

  • Redo : '누군가 무엇을 했다' => Redo 로그를 사용하여 과거의 데이터를 최신화(Roll forward)

 

  • Undo : '어떻게 하면 과거의 상태로 돌아갈 수 있는가' => undo 로그를 이용하여 변경취소(Roll back)
    • Undo 테이블 스페이스의 Undo 세그먼트에 보관 => Ring buffer 형태
    • Roll back에 쓰임
    • 읽기 일관성 유지에 쓰임 - 데이터 조회 시작 후 바뀐 데이터를 조회 시작지점으로 돌리기 위해 undo 사용
    • Commit 되지 않은 데이터 읽기에 쓰임(read committed)

 

  • 체크포인트 : 메모리와 디스크의 데이터 동기화 작업 시점
    • Roll forward에 걸리는 시간을 줄일 수 있다. 데이터베이스가 비정상 종료되면 마지막 체크포인트 이후의 트랜잭션 로그를 재생하여 복구하는데 체크포인트를 자주 사용하면 복구 대상 로그가 줄어든다.
    • 너무 자주 하면 디스크 IO 횟수가 늘어나 시스템 부하가 늘어나는 단점이 있다.

 

  • 인스턴스 복구(Instance Recovery) = Crash Recovery
    • 장애 발생 후 redo 로그를 이용하여 데이터를 최신 상태로 갱신
      1. Redo 로그 파일을 이용해 Roll forward 한다. 이 때, Undo 테이블스페이스도 포함되어 있다.
      2. 1번에서 Undo 가 최신 상태가 된 undo 세그먼트의 정보를 활용하여 Commit 되지 않은 것은 rollback 한다.

728x90
728x90
  • 값을 변경하는 작업(UPDATE)을 할 때는 변경하기 전의 값을 바라봐도 상관없는 검색(SELECT)를 제외하고는 아무도 그 값에 접근할 수 없도록 해야 한다.

→ UPDATE 문은 이러한 이유로 자동으로 LOCK을 걸게 된다. 이 경우 **LOCK에 의한 대기(WAIT)**가 발생한다.

  • Dead Lock : 서로가 상대가 보유하고 있는 LOCK을 기다리느라 교착 상태 발생 

 

  • 예시)
  • Transaction A는 트랜잭션을 끝내기 위해 Accounts 를 변경하고 Orders를 변경해야 한다. (Order를 참조하여 회계전표를 생성하고 Order의 상태를 전기 상태로 바꾸기 위해)
  • Transaction B는 Orders를 작업하고 Accounts를 작업해야 한다.(Accounts에 전기된 오더를 취소하기 위해 Accounts 테이블을 변경해야 할 때)
  • 이 때, 마침 서로가 원하는 다음 데이터가 각각 상대방이 Lock을 걸고 있는 데이터일 때 Dead Lock이 발생하게 된다.
-- TRANSACTION A - ACCOUNTS의 NO = 1 ROW의 LOCK을 보유
UPDATE ACCOUNTS SET COL1 = 2 WHERE NO = 1
UPDATE ORDERS   SET COL1 = 2 WHERE NO = 2

-- TRANSACTION B - ORDERS의 NO = 2 ROW의 LOCK을 보유
UPDATE ORDERS   SET COL1 = 3 WHERE NO = 2
UPDATE ACCOUNTS SET COL1 = 3 WHERE NO = 1

→ DEAD LOCK이 발생하여 ORA-00060 에러코드 발생 → 오라클에 의해 한 쪽 ROLLBACK 후 처리

  • LATCH : SGA(메모리공간) 내부의 공유 데이터에 대한 배타적인 LOCK 보장
    • SGA에 접근하는 프로세스는 원하는 영역에 대한 LATCH를 획득해야 접근할 수 있다.

  • OS에 의한 선점 스케줄링(preemption) 발생 가능 (DEADLOCK 같은 것)

Latch Wait

728x90
728x90

  • 익스텐트는 ‘연속된’ 블록의 집합으로 익스텐트의 위치와 블록 개수로 관리를 하여 관리 정보를 줄이는 역할을 한다.(블록 개별로 관리하면 많아서)
  • 세그먼트는 익스텐트의 집합이다. 사용자(개발자)가 생성하는 테이블, 인덱스 등 이외에 undo 세그먼트, 정렬 세그먼트 등이 있다.

  • 데이터 파일 안에는 서로 다른 세그먼트들의 서로 다른 익스텐트들이 혼재해 있다. 즉 물리적으로 같은 세그먼트의 데이터들이 모여있는 것이 아니다.
  • 테이블 스페이스는 세그먼트를 분류하여 보관하기 위한 공간. 한 개 이상의 데이터 파일로 구성되어 있다.
  • 세그먼트는 ‘PCTFREE’와 ‘PCTUSED’ 파라미터를 사용하여 세그먼트 내의 빈 블록 공간을 점검하고 관리한다. 세그먼트 안에 공간이 부족하면 ‘익스텐트’를 추가하여 빈 블록을 늘린다.
  • ROWID : 데이터 행의 주소로, 데이터 파일 번호, 블록 번호, 행 번호 등의 정보로 구성되어 있다.
  • 테이블 스페이스가 가진 여유 공간이 부족할 때는 데이터 파일 확장 또는 데이터 파일 추가 작업을 해야 한다.

 

  • OS 블록과 Oracle 블록은 서로 매핑되어 있는 것이 아니다.
    • Oracle block은 오라클이 OS에게 요청하는 IO의 가장 작은 단위이다.
    • OS block은 OS의 read/write의 가장 작은 단위이다.
    • Oracle block 단위가 8kb 이고 OS block 단위가 4kb일 때, 오라클의 8kb 만큼의 데이터를 읽어오는 요청을 OS에게 보내면 OS는 8kb 만큼의 데이터 요청을 4kb로 각각 분해하여 동작할 것이다.

728x90
728x90

 

  • lsnrctl start : 기본 리스너 기동 명령어
  • listener.ora : 리스너 설정 파일
  • tnsnames.ora : Connection desripter, 커넥션에 필요한 정보가 담긴 파일 즉, 데이터베이스의 주소가 담긴 파일
  • sqlnet.ora : DCD(Dead connection detection) 기능, SDU 등을 사용하기 위한 파라미터들 설정 파일
    • *DCD : 검출 패킷을 클라이언트에게 보내 확인, 클라이언트의 데이터베이스와의 연결 해제 없이 갑작스러운 서버 다운 및 충돌로 인한 disconnet를 감지하기 위한 기능.
    • Session Data Unit (SDU) 설정 : 오라클 클라이언트와 서버 사이의 네트워크 통신에서 주고받는 데이터 단위 설정 

Connection Pool

  • 일반적으로 서버 프로세스를 생성하는 것은 무거운 작업이므로 적을수록 좋다.
  • 서버 프로세스를 미리 여러 개 생성해두고 client가 DB에 접근할 때 Pool에서 꺼내 사용하는 Connection Pool 등장

 

 

 

 

 

 

 

 

728x90
728x90

오라클의 4가지 상태

  • OPEN - 데이터를 처리할 수 있는 상태, 즉, SQL을 처리할 수 있는 상태
  • MOUNT - 데이터 파일 등에 접근할 수 있는 상태(컨트롤 파일을 읽은 상태)
  • NOMOUNT - 백그라운드 프로세스와 공유메모리가 존재하는 상태
  • SHUTDOWN - 정지 상태

 

  • SHUTDOWN → NOMOUNT 단계 : 파라미터를 읽어서 백그라운드 프로세스를 기동하고 공유 메모리 할당한다.
  • NOMOUNT → MOUNT 단계 : 컨트롤 파일을 읽는다.
  • MOUNT → OPEN 단계 : 데이터 파일, 리두 로그 파일 등을 확인한다.

오라클의 인스턴스

  • 인스턴스는 백그라운드 프로세스와 공유 메모리를 의미한다.
  • 즉, NOMOUNT 상태는 인스턴스가 기동한 상태이다.
  • 일반적으로 데이터베이스와 1대1 대응하지만 RAC(Real Application Clusters)를 사용하는 경우에는 1대1 대응이 아니다.

인스턴스를 표시한 oracle의 대략적인 구조

  • 서버 프로세스는 인스턴스에 포함되지 않는 사용자(Client)와의 연결을 지원하기 위한 별도의 프로세스.
  • 서버 프로세스는 Dedicated Server Process와 Shared Server Process 두 가지 형태로 나뉜다.
    • Dedicated Server Process : 특정 클라이언트를 전담하는 서버 프로세스
    • Shared Server Process : Dispatcher가 요청을 수신하여 Request Queue에 담으면 그것을 처리하는 서버 프로세스

 

728x90
728x90

 

SQL문 분석 with 옵티마이저

SQL과 실행 계획

- SQL은 처리 방법(절차)를 기술하지 않는다.(이렇게 저렇게 해라라는 방법에 대한 기술이 없다)

- 대신 옵티마이저(파서)라고 불리는 기능이 실행 계획(plan)이라는 처리 방법을 생성한다. 이 작업은 서버 프로세스SQL문 분석에 해당하는 작업이다.

- 실행 계획은 규칙 기반(rule base)과 비용 기반(cost base)라는 알고리즘을 가지고 생성한다. 하지만 규칙 기반은 더 이상 쓰이지 않아 비용 기반만 고려한다.

- 비용 기반이란 '처리 시간이나 I/O 횟수가 가장 적을 것으로 예상되는 처리 방법이 최상'이라는 알고리즘이다. 

- 이 비용을 계산하기 위해서 여러 통계 정보를 사용한다.

옵티마이저 동작 원리

- 비용 계산을 위해 데이터 딕셔너리 뷰의 USER_TAB_STATISTICS와 같은 통계 정보를 이용한다.

비용 계산에 필요한 정보들

실행 계획 수립의 한계와 공유 풀(Shared Pool)

 어떤 처리 방법이 가장 좋은지(비용이 적은지)를 판단하기 위해서는 모든 처리 방법의 비용을 비교해야 한다. 모든 처리 방법을 비교한다는 것은 수 많은 경우의 수에 대한 예상치를 계산해야하기 때문에 그 자체로 비용(자원)이 많이 든다. 즉, 분석에 드는 CPU 자원이 아까워지는 현상이 발생할 것이다. 그럼 이 실행 계획을 공유해서 자원 소비를 줄이는 방법을 자연스럽게 생각하게 된다. Ch3에서 이미 캐시와 공유 메모리에 대해 알아보았다. 이 실행계획도 서버 프로세스들이 서로 공유한다면 실행 계획을 수립하는 데 사용되는 자원을 줄일 수 있다. (*또한, 선정된 실행 계획이 무조건 가장 좋은 계획이 아닐 수 있다. 어디까지나 예상이기 때문이다. SQL 튜닝(인덱스 등을 활용한)을 통해 더 나은 실행 계획을 세우도록 유도할 수 있다.)

 공유 풀(Shared Pool)이라는 공간이 공유 메모리 영역(SGA)에 존재한다. 공유 메모리는 대부분 버퍼 캐시로 사용되고 남은 일부가 공유 풀로 사용되어 그 안에 통계 정보나 실행 계획 등의 캐시 데이터가 저장된다.

 실행 계획 등은 라이브러리 캐시(Libary Cache) 공간에 캐싱된다.

공유 풀의 구조

 

같은 SQL은 같은 실행 계획을 사용한다. 그러면 오라클은 어떻게 같은 SQL을 판단할까?

바인드 변수의 사용

SELECT ID, CUST_NAME, TEL_NO
  FROM CUST_INFO
 WHERE ID = '001'

SELECT ID, CUST_NAME, TEL_NO
  FROM CUST_INFO
 WHERE ID = '002'

-- 위 두 가지 SQL은 오라클이 다른 SQL로 취급한다. 오라클은 SQL문을 하나의 문자열로 간주하기 떄문이다.

SELECT ID, CUST_NAME, TEL_NO
  FROM CUST_INFO
 WHERE ID = :P1

SELECT ID, CUST_NAME, TEL_NO
  FROM CUST_INFO
 WHERE ID = :P1

-- 바인드 변수를 사용하여 SQL을 실행하면 오라클은 'P1'에 어떤 값이 담기든 같은 SQL로 간주한다.
-- 같은 SQL이 실행된 것으로 판단하여 이전에 캐시에 저장해둔 실행 계획을 가져와 SQL을 처리한다.

- Hard Parse : 공유 풀에 실행 계획이 없어 실행 계획을 새로 생성. 위 경우에 해당한다. 사용자(Client)는 같은 SQL이라고 생각해도 오라클은 그렇게 판단하지 않는다.

- Soft Parse : 공유 풀에 있는 실행 계획을 재사용. 아래 경우에 해당한다.

 

 이처럼 공유 풀과 바인드 변수를 사용하여 소프트 파스를 유도하여 실행 계획 수립에 대한 비용을 낮추는 방법이 사용된다.

 

공유 풀 정보 with statspack report

- Statspack은 오라클의 분석용 도구이다.

예시 정보
실제 예시

- 위 통계를 보고 parse를 위한 CPU사용량 등이 적절한 지에 대한 판단을 할 수 있을 것

정리

- SQL문은 처리 방법에 대한 기술이 없어 분석(parse)을 통해 처리 방법(실행 계획)을 수립한다.

- 실행 계획에도 좋고 나쁨이 있다. 

- 실행 계획을 생성하는 데 사용되는 비용을 줄이기 위해 공유 풀(라이브러리 캐시)에 실행 계획을 캐시해서 재활용.

728x90

+ Recent posts