DB·SQL 실무 가이드 · Part 1

실습 환경과 SQL이 실행되는 구조

따라 할 수 있는 Oracle 실습 환경을 만들고 SQL 실행 구조를 잡기

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

왜 Oracle 기준인가Docker로 띄우기사용자와 스키마SQL 실행 경로실습 테이블
01

이 코스가 Oracle을 기준으로 삼는 이유

국내 B2B 환경, 특히 공공·금융·제조 SI에서 실제로 만나는 DB는 대부분 Oracle입니다. 클라우드 신규 구축에서는 PostgreSQL이나 MySQL 채택이 늘고 있지만, 이미 돌아가는 기간계 시스템을 다루는 일이 훨씬 많습니다.

여기에 국산 DBMS인 Tibero가 Oracle 호환을 전면에 내세우고 있어서, Oracle 기준으로 익혀 두면 공공 사업에서 만나는 Tibero까지 대부분 그대로 통합니다.

다른 DBMS를 쓰고 있어도 이 코스의 대부분은 그대로 쓸모가 있습니다. 인덱스가 언제 안 타는지, 실행계획을 어떻게 읽는지, 트랜잭션이 어떻게 꼬이는지는 제품과 무관하기 때문입니다. 갈리는 것은 문법의 표면뿐이고, 그 차이는 부록에 대조표로 모아 두었습니다.

개념은 공통
90%

인덱스 구조, 실행계획, 트랜잭션 격리수준, 락, 조인 방식은 어느 DBMS든 같은 원리로 동작합니다. 한 번 익히면 옮겨 갑니다.

문법은 갈림
10%

페이징, 문자열 결합, 날짜 함수, NULL 정렬 같은 표면 문법이 다릅니다. 부록의 대조표로 확인합니다.

운영 도구는 제품별
차이 큼

진단 도구와 통계 관리 방식은 제품마다 다릅니다. 트랙 D에서 Oracle 기준으로 다룹니다.

02

Docker로 실습 환경을 만듭니다

Oracle은 설치가 무겁다는 인식이 있지만, Oracle Database Free 에디션을 쓰면 컨테이너 한 줄로 끝납니다. 개발과 운영 모두 무료로 쓸 수 있고, 사용자 데이터 12GB와 CPU 2개라는 제한이 있습니다. 학습과 실습에는 넉넉합니다.

Docker 코스에서 다룬 포트 매핑과 named volume이 여기서 그대로 쓰입니다. 컨테이너를 지워도 데이터를 남기려면 볼륨을 붙여 둡니다.

Oracle Database Free 컨테이너 실행bash
docker run -d --name oracle-free \
  -p 1521:1521 \
  -e ORACLE_PASSWORD=dev \
  -v oracle-data:/opt/oracle/oradata \
  gvenzl/oracle-free:latest

첫 실행은 초기화에 몇 분 걸립니다. docker logs -f oracle-free 로 DATABASE IS READY TO USE 메시지를 확인하고 접속합니다. 커뮤니티 이미지 대신 Oracle 공식 레지스트리(container-registry.oracle.com/database/free)를 써도 됩니다.

실습용 사용자 만들기sql
-- 관리자로 접속한 뒤
ALTER SESSION SET CONTAINER = FREEPDB1;

CREATE USER study IDENTIFIED BY study
  QUOTA UNLIMITED ON USERS;

GRANT CONNECT, RESOURCE TO study;
GRANT SELECT_CATALOG_ROLE TO study;  -- 실행계획 조회용

SELECT_CATALOG_ROLE 은 트랙 B에서 V$ 뷰로 실행계획을 볼 때 필요합니다. 운영 DB에서는 함부로 부여하지 않습니다.

버전 확인

이미지 태그와 에디션별 제한은 시간이 지나면 달라집니다. 실습 전에 Oracle 공식 문서에서 현재 Free 에디션 스펙을 한 번 확인하는 편이 안전합니다.

03

Oracle에서는 사용자가 곧 스키마입니다

다른 DBMS를 쓰다 오면 가장 먼저 헷갈리는 부분입니다. MySQL의 데이터베이스나 MSSQL의 스키마와 달리, Oracle에서는 사용자를 만들면 같은 이름의 스키마가 함께 생깁니다. 테이블은 그 사용자 소유로 들어갑니다.

그래서 다른 사용자의 테이블을 조회하려면 `소유자.테이블명` 으로 쓰거나 권한을 받아야 합니다. 운영 DB에서 조회 권한만 받은 계정으로 접속했을 때 테이블이 안 보이는 이유가 대개 이것입니다.

  • 접속 문자열은 `사용자/비밀번호@호스트:포트/서비스명` 형태입니다. 위 컨테이너라면 `study/study@localhost:1521/FREEPDB1` 입니다.
  • CLI는 `sqlplus` 를 씁니다. Tibero는 `tbsql` 로 이름만 다르고 사용법은 거의 같습니다.
  • GUI는 무료인 Oracle SQL Developer나 DBeaver를 흔히 씁니다. 폐쇄망에서는 반입 승인이 필요할 수 있으니 CLI도 다룰 줄 아는 편이 안전합니다.
  • `USER_TABLES` 는 내 소유 테이블, `ALL_TABLES` 는 내가 접근 가능한 테이블, `DBA_TABLES` 는 전체입니다. 셋을 구분하면 권한 문제를 빨리 진단합니다.
접속과 현재 상태 확인sql
-- 컨테이너 안에서 접속
docker exec -it oracle-free sqlplus study/study@localhost:1521/FREEPDB1

-- 내가 누구이고 어디에 붙어 있는지
SELECT USER FROM DUAL;
SELECT SYS_CONTEXT('USERENV', 'DB_NAME') AS db_name FROM DUAL;

-- 내 소유 테이블 목록
SELECT table_name FROM user_tables ORDER BY table_name;

SELECT 만 쓰고 FROM 을 생략할 수 없는 것이 Oracle의 특징입니다. 그래서 한 행짜리 더미 테이블인 DUAL 을 씁니다.

04

SQL 한 문장이 실행되기까지

쿼리를 던지면 바로 데이터를 읽는 것이 아닙니다. 이 경로를 알아야 트랙 B의 실행계획과 바인드 변수 이야기가 이해됩니다.

핵심은 옵티마이저가 실행 방법을 스스로 정한다는 점입니다. 우리는 무엇을 가져올지 쓰고, 어떻게 가져올지는 옵티마이저가 결정합니다. 성능 튜닝이란 결국 옵티마이저가 좋은 결정을 하도록 조건을 만들어 주는 일입니다.

1. 파싱
문법과 권한 확인

문법이 맞는지, 그 객체에 접근할 권한이 있는지 확인합니다. 이미 같은 SQL이 공유 풀에 있으면 여기서 재사용합니다.

2. 최적화
옵티마이저

통계정보를 근거로 여러 실행 방법의 비용을 비교해 하나를 고릅니다. 통계가 실제와 다르면 여기서 잘못된 선택이 나옵니다.

3. 실행
데이터 읽기

정해진 계획대로 블록을 읽습니다. 메모리에 있으면 빠르고, 디스크까지 가면 느립니다.

하드 파싱
비용

매번 값이 박힌 SQL을 던지면 문장이 매번 달라져서 1~2단계를 새로 합니다. 바인드 변수를 쓰면 이 과정을 건너뜁니다. Part 9에서 다룹니다.

05

실습용 테이블을 만들어 둡니다

이후 파트의 예제는 아래 테이블을 기준으로 합니다. 주문과 고객이라는 익숙한 구조이고, 성능 실습을 위해 행을 넉넉히 넣습니다.

실습 테이블과 데이터sql
CREATE TABLE customer (
  customer_id  NUMBER        PRIMARY KEY,
  customer_nm  VARCHAR2(100) NOT NULL,
  grade_cd     VARCHAR2(10),
  reg_dt       DATE          DEFAULT SYSDATE
);

CREATE TABLE orders (
  order_id     NUMBER        PRIMARY KEY,
  customer_id  NUMBER        NOT NULL,
  order_dt     DATE          NOT NULL,
  status_cd    VARCHAR2(10)  NOT NULL,
  amount       NUMBER(12, 2) NOT NULL,
  CONSTRAINT fk_orders_customer
    FOREIGN KEY (customer_id) REFERENCES customer (customer_id)
);

-- 고객 1만 건
INSERT INTO customer (customer_id, customer_nm, grade_cd, reg_dt)
SELECT LEVEL,
       '고객' || LEVEL,
       CASE MOD(LEVEL, 3) WHEN 0 THEN 'VIP' WHEN 1 THEN 'GOLD' ELSE 'BASIC' END,
       SYSDATE - MOD(LEVEL, 1000)
FROM DUAL CONNECT BY LEVEL <= 10000;

-- 주문 50만 건
INSERT INTO orders (order_id, customer_id, order_dt, status_cd, amount)
SELECT LEVEL,
       MOD(LEVEL, 10000) + 1,
       SYSDATE - MOD(LEVEL, 700),
       CASE MOD(LEVEL, 10) WHEN 0 THEN 'CANCEL' WHEN 1 THEN 'READY' ELSE 'DONE' END,
       ROUND(DBMS_RANDOM.VALUE(1000, 500000), -2)
FROM DUAL CONNECT BY LEVEL <= 500000;

COMMIT;

CONNECT BY LEVEL 은 Oracle에서 원하는 개수만큼 행을 만들어 내는 관용적인 방법입니다. Part 3의 계층 쿼리에서 다시 다룹니다.

이 파트의 목표

환경을 갖추는 것 자체가 목표입니다. 이후 모든 파트는 여기서 만든 테이블로 직접 실행해 보면서 읽는 것을 전제로 합니다. 눈으로만 읽으면 실행계획 파트에서 막힙니다.

체크

이 파트 완료 기준