Post

[Oracle 튜닝] 자전거 대여 및 유지보수 스키마 구성

[Oracle 튜닝] 자전거 대여 및 유지보수 스키마 구성

🧾 실습 개요

대용량 자전거 대여 및 부품 교체 이력을 다루는 BIKER 스키마를 기반으로, SQL 성능 분석 및 튜닝 실습을 진행합니다.

본 포스팅에서는 실습 환경 구성을 소개합니다.


📂 스키마명

  • BIKER

🧩 주요 테이블 및 컬럼 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
📌 BIKE
  - ID (PK)
  - MODEL_ID
  - NAME

📌 BIKE_MODEL
  - ID (PK)
  - NAME

📌 BIKE_PART
  - ID (PK)
  - NAME

📌 BIKE_PART_CHANGE_RECORD
  - ID (PK)
  - BIKE_ID
  - PART_ID
  - ATTACH_TIME
  - DETACH_TIME
  - REMARK

📌 BIKE_PART_CHANGE_LOG
  - ID (PK)
  - CREATED_TIME
  - WORKER_ID
  - ATTACH_PART_RECORD_ID
  - DETACH_PART_RECORD_ID  

📌 BIKE_LOG
  - ID (PK)
  - BIKE_ID
  - SERVICE_TYPE  -- 'RENT' 또는 'MAINTENANCE'
  - START_TIME
  - END_TIME


📦 BIKER 실습 데이터 생성 순서

  1. BIKE_MODEL (모델 20종)
  2. BIKE (자전거 2000대)
  3. BIKE_PART (부품 100종)
  4. BIKE_PART_CHANGE_RECORD (교체이력 2만건)
  5. BIKE_PART_CHANGE_LOG (탈거+장착 로그 총 4만건)
  6. BIKE_LOG (100만건 + MAINTENANCE 1만건)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
-- 1. BIKE_MODEL: 모델 20종
BEGIN
  FOR i IN 1..20 LOOP
    INSERT INTO BIKE_MODEL (ID, NAME)
    VALUES ('M' || LPAD(i, 2, '0'), 'Model_' || i);
  END LOOP;
  COMMIT;
END;
/

-- 2. BIKE: 자전거 2000대
BEGIN
  FOR i IN 1..2000 LOOP
    INSERT INTO BIKE (ID, MODEL_ID, NAME)
    VALUES (
      'B' || LPAD(i, 4, '0'),
      'M' || LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 21)), 2, '0'),
      'Bike_' || i
    );
    IF MOD(i, 500) = 0 THEN COMMIT; END IF;
  END LOOP;
  COMMIT;
END;
/

-- 3. BIKE_PART: 부품 100종
BEGIN
  FOR i IN 1..100 LOOP
    INSERT INTO BIKE_PART (ID, NAME)
    VALUES ('P' || LPAD(i, 3, '0'), 'Part_' || i);
  END LOOP;
  COMMIT;
END;
/

-- 4. BIKE_PART_CHANGE_RECORD - 교체 이력 2만 건 (7/7일)
BEGIN
  FOR i IN 1..20000 LOOP
    DECLARE
      v_attach_time DATE := TO_DATE('2025-07-07 08:00:00', 'YYYY-MM-DD HH24:MI:SS') + DBMS_RANDOM.VALUE * (8/24);
      v_detach_time DATE := v_attach_time + DBMS_RANDOM.VALUE * (1/24);
    BEGIN
      INSERT INTO BIKE_PART_CHANGE_RECORD (
        ID, BIKE_ID, PART_ID, ATTACH_TIME, DETACH_TIME, CREATE_TIME, UPDATE_TIME, REMARK
      ) VALUES (
        'PCR' || LPAD(i, 6, '0'),
        'B' || LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 2001)), 4, '0'),
        'P' || LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 101)), 3, '0'),
        v_attach_time,
        v_detach_time,
        v_attach_time,
        v_detach_time,
        'CHANGE_FINISHED'
      );
    END;
  END LOOP;
  COMMIT;
END;
/

-- 5. BIKE_PART_CHANGE_LOG - 교체 로그 4만 건 (2건/RECORD)
BEGIN
  FOR rec IN (
    SELECT ID, BIKE_ID, PART_ID, ATTACH_TIME, DETACH_TIME
    FROM BIKE_PART_CHANGE_RECORD
    WHERE TRUNC(ATTACH_TIME) = TO_DATE('2025-07-07', 'YYYY-MM-DD')
  ) LOOP
    -- 5.1. 탈거 로그
    INSERT INTO BIKE_PART_CHANGE_LOG (
      ID, BIKE_ID, PART_ID, DETACH_PART_RECORD_ID, CREATED_TIME, WORKER_ID, PREV_USED_KM, USED_KM, REMARK
    ) VALUES (
      SEQ_CHANGE_LOG_ID.NEXTVAL,
      rec.BIKE_ID,
      rec.PART_ID,
      rec.ID,
      rec.DETACH_TIME,
      'WORKER' || LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 11)), 2, '0'),
      TRUNC(DBMS_RANDOM.VALUE(500, 5000)),
      TRUNC(DBMS_RANDOM.VALUE(100, 1000)),
      '자동생성: 탈거'
    );

    -- 5.2. 장착 로그
    INSERT INTO BIKE_PART_CHANGE_LOG (
      ID, BIKE_ID, PART_ID, ATTACH_PART_RECORD_ID, CREATED_TIME, WORKER_ID, USED_KM, REMARK
    ) VALUES (
      SEQ_CHANGE_LOG_ID.NEXTVAL,
      rec.BIKE_ID,
      rec.PART_ID,
      rec.ID,
      rec.ATTACH_TIME,
      'WORKER' || LPAD(TRUNC(DBMS_RANDOM.VALUE(1, 11)), 2, '0'),
      0,
      '자동생성: 장착'
    );
  END LOOP;
  COMMIT;
END;
/

-- 6. BIKE_LOG - 자전거 운영 로그 100만 건

-- 6.1. 시퀀스 생성
CREATE SEQUENCE SEQ_BIKE_LOG START WITH 1 INCREMENT BY 1;

-- 6.2. 100만 건 INSERT (PL/SQL 루프)
BEGIN
  FOR i IN 1..1000000 LOOP
    INSERT INTO BIKE_LOG (
      ID,
      CREATE_TIME
    ) VALUES (
      SEQ_BIKE_LOG.NEXTVAL,
      TRUNC(SYSDATE - DBMS_RANDOM.VALUE(0, 365))  -- 최근 1년 내 날짜
    );

    -- 1만건마다 커밋
    IF MOD(i, 10000) = 0 THEN
      COMMIT;
    END IF;
  END LOOP;
  COMMIT;
END;
/

-- 6.3. SERVICE_TYPE 랜덤 분포 설정 (RENT 95%, MAINTENANCE 5%)
-- 6.3.1. 임시 컬럼 생성
ALTER TABLE BIKE_LOG ADD TEMP_RND NUMBER;

-- 6.3.2. 무작위 값 부여
UPDATE BIKE_LOG
SET TEMP_RND = DBMS_RANDOM.VALUE;

-- 6.3.3. 서비스유형 분기
UPDATE BIKE_LOG
SET SERVICE_TYPE = CASE
  WHEN TEMP_RND <= 0.05 THEN 'MAINTENANCE'
  ELSE 'RENT'
END;

-- 6.3.4. 임시 컬럼 제거
ALTER TABLE BIKE_LOG DROP COLUMN TEMP_RND;

COMMIT;

🔹 BIKE_MODEL 샘플

IDNAMETYPE
M01Model_1하이브리드
M02Model_2스포츠
M03Model_3전기
M20Model_20스포츠

🔹 BIKE 샘플

IDMODEL_IDSTATUS
B0001M13ACTIVE
B0011M17INACTIVE
B0021M02ACTIVE

🔹 BIKE_PART 샘플

IDNAMEMAX_USAGE_KMMAX_USAGE_DAY
P001Part_1516250
P002Part_262898
P020Part_20938218

🔹 BIKE_PART_CHANGE_RECORD 샘플

IDBIKE_IDPART_IDATTACH_TIMEDETACH_TIMEREMARK
PCR000187B0237P0882025-07-07 11:56:062025-07-07 12:53:20CHANGE_FINISHED
PCR000188B0236P0112025-07-07 15:59:512025-07-07 16:24:53CHANGE_FINISHED

🔹 BIKE_PART_CHANGE_LOG 샘플

IDPART_IDBIKE_IDATTACH_PART_RECORD_IDDETACH_PART_RECORD_IDCREATED_TIMEWORKER_IDUSED_KMREMARK
193P044B0430PCR000282NULL2025-07-07 09:28:28WORKER070장착
194P036B1641NULLPCR0002832025-07-07 13:02:23WORKER01183탈거

🔹 BIKE_LOG 샘플

IDBIKE_IDUSER_IDSTART_TIMEEND_TIMEDISTANCE_KMCREATE_TIMESERVICE_TYPE
359B1251U3952025-07-07 11:17:422025-07-07 11:42:420.682025-07-07 11:42:42RENT
376B1232U2722025-07-07 09:18:192025-07-07 09:38:191.652025-07-07 09:38:19MAINTENANCE

  • BIKE_PART_CHANGE_RECORD은 모두 CHANGE_FINISHED 상태
  • BIKE_PART_CHANGE_LOG는 각 RECORD당 2건 생성됨 (장착 + 탈거)
  • SERVICE_TYPE은 ‘RENT’, ‘MAINTENANCE’ 2종으로 구분됨

✅ 조회 실습 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT  
  R.ID,
  R.BIKE_ID,
  R.PART_ID,
  R.ATTACH_TIME,
  R.DETACH_TIME, 
  R.REMARK,
  D.WORKER_ID AS DETACH_WORKER,
  (SELECT MAX(BL.END_TIME)
     FROM BIKE_LOG BL
    WHERE BL.BIKE_ID = D.BIKE_ID
      AND BL.END_TIME < D.CREATED_TIME
  ) AS PREV_RENT_END_TIME
FROM
  BIKE_PART_CHANGE_RECORD R,
  BIKE_PART_CHANGE_LOG D,
  BIKE_PART_CHANGE_LOG A
WHERE 1=1
  AND D.DETACH_PART_RECORD_ID = R.ID
  AND D.DETACH_PART_RECORD_ID = A.ATTACH_PART_RECORD_ID 
  AND D.CREATED_TIME BETWEEN TO_DATE('20250707110000', 'YYYYMMDDHH24MISS')
                        AND TO_DATE('20250707110959', 'YYYYMMDDHH24MISS');
This post is licensed under CC BY 4.0 by the author.