Reference · DBMS

DBMS 실전 가이드

CS 백과의 데이터베이스 도메인이 개념 카드라면, 이 페이지는 실전이다 — 민원 접수 시스템이라는 예제 스키마 하나로 정규화 분해 → SQL 문법 → 트랜잭션 → 인덱스 튜닝까지 관통한다. 시선아이티 챗봇 직무 = "내부 DB 조회·연동"이므로 스키마 설계와 쿼리는 직무 그 자체. SQL은 우리 코드와 같은 SQLite 방언 기준, 차이는 각주로.

1키와 무결성 제약 — 스키마의 문법

정규화·인덱스·조인 전부 키 개념 위에 선다. 용어부터 정확히 — 면접에서 "후보키랑 기본키 차이"는 단골 첫 질문.

키 용어 정리 — 유일성(식별 가능)과 최소성(필요한 최소 조합)의 조합
용어정의예시
슈퍼키행을 유일하게 식별하는 컬럼(조합) 전부 — 최소성 불필요(주민번호+이름), (접수번호)
후보키슈퍼키 중 최소성까지 만족 — 유일성·최소성접수번호, 주민번호
기본키 (PK)후보키 중 대표로 선택한 것. NULL 불가·테이블당 1개접수번호
대리키실무를 위해 인공 생성한 후보키 — 자동증가·UUIDcomplaint_id INTEGER AUTOINCREMENT
외래키 (FK)다른 테이블의 PK/UNIQUE를 참조 — 참조 무결성의 닻complaint.citizen_id → citizen
복합키2개 이상 컬럼 조합으로 식별 — 순서가 인덱스·정규화에 영향(complaint_id, type_name)
무결성 제약 3종 — "DB가 스스로 지키는 규칙"
제약의미선언 수단
개체 무결성PK는 NULL 불가·중복 불가. 행의 정체성 보장PRIMARY KEY → NOT NULL + UNIQUE 자동 부여
참조 무결성FK는 참조 대상에 없는 값 불가. 고아 행 방지REFERENCES + ON DELETE (CASCADE / SET NULL / RESTRICT)
도메인 무결성컬럼값은 정의된 타입·범위·형식을 지켜야타입, CHECK, NOT NULL, DEFAULT, ENUM

NULL은 값이 아니라 '알 수 없음' — NULL = NULL 비교도 UNKNOWN이므로 IS NULL로만 검사한다(3치 논리). NOT IN (… NULL 포함 목록)이 빈 결과를 내는 함정도 이 논리에서 나온다.

예제 스키마 전체 — 이 테이블들로 §2~§5를 진행한다
-- 민원 접수 시스템 (SQLite 방언 — 우리 코드와 동일)
CREATE TABLE citizen (
  citizen_id  INTEGER PRIMARY KEY,          -- 대리키
  name        TEXT NOT NULL,
  phone       TEXT UNIQUE                   -- 후보키
);

CREATE TABLE department (
  dept_id     INTEGER PRIMARY KEY,
  dept_name   TEXT NOT NULL UNIQUE,
  floor       INTEGER CHECK (floor BETWEEN 1 AND 20)
);

CREATE TABLE complaint (
  complaint_id INTEGER PRIMARY KEY AUTOINCREMENT,
  citizen_id   INTEGER NOT NULL REFERENCES citizen(citizen_id),
  dept_id      INTEGER REFERENCES department(dept_id)
               ON DELETE SET NULL,         -- 부서 폐지 → 미배정으로
  status       TEXT NOT NULL DEFAULT '접수'
               CHECK (status IN ('접수','처리중','완료','반려')),
  created_at   TEXT NOT NULL DEFAULT (datetime('now'))
  -- PostgreSQL: AUTOINCREMENT 대신 GENERATED ALWAYS AS IDENTITY
);

-- 복합키는 별도 테이블로: 민원 1 : N 유형
CREATE TABLE complaint_type (
  complaint_id INTEGER NOT NULL REFERENCES complaint(complaint_id),
  type_name    TEXT    NOT NULL,
  PRIMARY KEY (complaint_id, type_name)    -- 복합 PK
);
citizen PK citizen_id complaint PK complaint_id FK citizen_id, dept_id complaint_type PK (complaint_id, type_name) department PK dept_id 1 N 1 : N N 1
ERD (까마귀 발 표기) — citizen 1:N complaint N:1 department, complaint 1:N complaint_type. SI 설계 산출물의 핵심이 이 그림이다.

2정규화 실전 — 접수부 한 장의 분해 여정

정규화는 "함수 종속(FD: A → B, A가 정해지면 B도 정해진다)을 키에 맞게 재배치"하는 작업. 아래 4단계를 그대로 따라가면 면접 화이트보드에서도 그릴 수 있다.

삽입 이상

민원 0건인 신설 부서(‘문화과’)를 등록할 방법이 없다 — 부서 정보는 민원 행에 묶여 있어서.

갱신 이상

도시과가 3층→5층으로 이전하면 도시과 민원 행 전부를 갱신해야 한다. 하나라도 놓치면 같은 부서의 층 정보가 둘로 갈라진다.

삭제 이상

환경과의 마지막 민원을 삭제하면 환경과의 위치 정보까지 함께 사라진다.

이 세 가지가 정규화의 '왜'다 — 각 단계는 이 이상들을 구조적으로 제거한다.

  1. 비정규

    엑셀식 접수부 — 출발점

    FD함수 종속 없음 (한 셀에 복수값)

    접수번호민원인전화담당부서부서위치민원유형처리자
    C-1001김민원010-1111도시과3층소음, 쓰레기박처리
    C-1002이시민010-2222환경과2층악취최담당

    『민원유형』 셀에 복수값('소음, 쓰레기'). 관계형 모델의 전제(1셀 1값) 위반 — 쿼리·인덱스·제약 모두 불가.

    → 1NF로

  2. 1NF

    원자값 — 복수값을 행으로 펼친다

    FDPK = (접수번호, 민원유형) → 나머지 전체

    접수번호민원인전화담당부서부서위치민원유형처리자
    C-1001김민원010-1111도시과3층소음박처리
    C-1001김민원010-1111도시과3층쓰레기박처리
    C-1002이시민010-2222환경과2층악취최담당

    이제 쿼리가 가능하지만 C-1001의 민원인·부서 정보가 유형 수만큼 중복. PK가 복합키가 됐다 — 2NF 검사 대상.

    → 2NF로

  3. 2NF

    부분 함수 종속 제거 — 복합키의 일부에만 종속되는 속성 분리

    FD(접수번호, 유형)이 PK인데 접수번호 → 민원인·전화·부서·처리자

    분해 결과구성
    민원 (PK: 접수번호)민원인, 전화, 담당부서, 부서위치, 처리자
    민원유형 (PK: 접수번호+유형)유형명 — 유형 수만큼만 행 존재

    민원인·부서·처리자는 유형과 무관하게 접수번호만으로 결정(부분 종속). 유형을 추가할 때마다 같은 값 반복.

    → 민원 테이블 / 민원유형 테이블로 분해

  4. 3NF

    이행 함수 종속 제거 — 키가 아닌 컬럼이 키가 아닌 컬럼을 결정

    FD접수번호 → 담당부서 → 부서위치 (A→B→C 이행 종속)

    최종 테이블 (3NF)키 · 관계
    민원 (PK: complaint_id)citizen_id FK, dept_id FK, handler, status
    민원유형 (PK: complaint_id+type_name)민원 1 : N 유형
    부서 (PK: dept_id)dept_name, floor — 부서 고유 정보
    민원인 (PK: citizen_id)name, phone

    부서위치는 민원이 아니라 부서의 속성. 부서 이전 시 해당 부서 민원 행을 전부 갱신해야 하고, 마지막 민원이 삭제되면 부서 위치 정보도 소실.

    → 부서 테이블 분리 → 최종 3NF

BCNF 이후 — 면접 가드 레벨
-- BCNF 검증: "모든 결정자가 후보키인가?"
-- 3NF 통과 후에도 결정자가 후보키가 아닌 경우가 남을 수 있다.
-- 예) (처리자) → (부서) 면서 처리자가 후보키가 아니면 BCNF 위반.
--     → 처리자 테이블을 별도 분해. 실무 면접은 3NF 설계 + BCNF 개념 설명이면 충분.
-- 4NF(다치 종속)·5NF(조인 종속)는 다면 후보 속성·수강 이력 같은 특수 사례 — "존재는 안다"로 충분.

반정규화 — 정규화의 실행 순서를 거꾸로 타는 것

정규화는 갱신 이상을 없애지만 조인을 늘린다. 읽기 성능이 임계치를 넘을 때 측정 후 선택적으로 되돌린다. "정규화가 기본값, 반정규화는 측정된 예외" — 이 한 줄이 면접 표준 답.

기법예시대가
집계 컬럼부서 테이블에 민원 수 카운터 — COUNT(*) 회피증감 누락 시 부정합
요약 테이블월별 통계 테이블을 배치로 생성 — 대시보드 조회 가속배치 주기만큼 지연
컬럼 복제자주 조인되는 상태명을 민원 테이블에 중복상태 변경 시 이중 갱신

3SQL 문법 — 분류 · 실행 순서 · 조인 · 집계

문법 나열이 아니라 "왜 그 순서인가"를 아는 것이 목적. 실행 순서를 알면 절반의 왜가 풀린다.

SQL 명령 분류 — SI 문서·면접에서 이 다섯 묶음으로 말한다
분류명령비고
DDL (정의)CREATE · ALTER · DROP · TRUNCATE구조 자체를 다룬다
DML (조작)INSERT · UPDATE · DELETE행 데이터 변경
DQL (질의)SELECT교과서 분류. 현장은 SELECT를 DML에 포함시키기도
DCL (제어)GRANT · REVOKE권한 — 공공 SI 실무의 상수(보안심사)
TCL (트랜잭션)COMMIT · ROLLBACK · SAVEPOINT§4에서 실전

SELECT의 논리적 실행 순서

1. FROM / ON / JOIN2. WHERE3. GROUP BY4. HAVING5. SELECT6. DISTINCT7. ORDER BY8. LIMIT
  • WHERE에서 SELECT 별칭을 못 쓰는 이유 — 별칭이 붙는 SELECT가 WHERE보다 나중에 실행된다
  • 집계 결과를 걸러야 하면 WHERE가 아니라 HAVING — WHERE는 그룹화 이전, 개별 행 단계에서 돈다
  • ORDER BY 이후에야 LIMIT — '정렬된 상위 N개'가 성립하는 이유

JOIN — 6종, 결과셋의 모양으로 이해

종류결과쓰임
INNER JOIN양쪽 모두 일치하는 행만부서가 배정된 민원만
LEFT JOIN왼쪽 전부 + 오른쪽 일치(없으면 NULL)미배정 민원까지 전수
RIGHT JOIN오른쪽 전부 + 왼쪽 일치LEFT의 좌우 반전 — 실무는 LEFT로 통일해 쓰고 위치 바꾼다
FULL JOIN양쪽 전부 (MySQL 미지원 → UNION으로 흉내)양쪽 누락 검사
CROSS JOIN모든 조합 n×m기간×부서 격자 생성 등 의도적 사용
SELF JOIN같은 테이블 재조인민원인의 이전 민원 조회
JOIN 실전 — INNER / LEFT / SELF
-- INNER: 부서가 확정된 민원만 (ON이 일치하는 행)
SELECT c.complaint_id, c.handler, d.dept_name
FROM complaint c
JOIN department d ON d.dept_id = c.dept_id;

-- LEFT: 미배정(dept_id IS NULL) 민원도 누락 없이
SELECT c.complaint_id, COALESCE(d.dept_name, '미배정') AS dept
FROM complaint c
LEFT JOIN department d ON d.dept_id = c.dept_id;

-- SELF JOIN: 같은 민원인의 다른 민원 찾기
SELECT a.complaint_id AS mine, b.complaint_id AS other
FROM complaint a
JOIN complaint b
  ON a.citizen_id = b.citizen_id
 AND a.complaint_id < b.complaint_id;   -- 자기 자신·중복 제거

GROUP BY · HAVING · 윈도우 함수

집계 — HAVING이 필요한 순간과 OVER()가 주는 위력
-- 상태별 민원 수: GROUP BY(그룹화) + HAVING(그룹 필터)
SELECT status, COUNT(*) AS n
FROM complaint
GROUP BY status
HAVING COUNT(*) >= 10        -- WHERE로는 불가: 집계 이후 조건
ORDER BY n DESC;

-- 비율까지: 윈도우 함수로 전체 합을 각 행에 병렬 계산
SELECT status,
       COUNT(*) AS n,
       ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) AS pct
FROM complaint
GROUP BY status;             -- SUM(COUNT(*)) OVER () = 전체 민원 수
같은 문제 3가지 풀이 — 서브쿼리(N+1) → CTE+윈도우(권장) → 집계 윈도우
-- 같은 문제: "부서별 최신 민원 1건씩"을 3가지로

-- (1) 상관 서브쿼리 — 부서마다 다시 SELECT (N+1 실행, 느림)
SELECT c.* FROM complaint c
WHERE c.created_at = (SELECT MAX(x.created_at)
                      FROM complaint x
                      WHERE x.dept_id = c.dept_id);

-- (2) CTE + 윈도우 함수 — 그룹 내 순위 매겨 1등만 (권장)
WITH ranked AS (
  SELECT c.*,
         ROW_NUMBER() OVER (PARTITION BY dept_id
                            ORDER BY created_at DESC) AS rn
  FROM complaint c
)
SELECT complaint_id, dept_id, status, created_at
FROM ranked
WHERE rn = 1;

-- (3) 윈도우 함수 직접 — 전체 대비 부서 비중·누적까지 한 번에
SELECT dept_id, COUNT(*) AS n,
       SUM(COUNT(*)) OVER (ORDER BY COUNT(*) DESC) AS running_total
FROM complaint
GROUP BY dept_id;

DML 안전 습관 · DELETE vs TRUNCATE vs DROP

UPDATE/DELETE는 WHERE와 함께 — 실행 전 SELECT로 대상 확인
-- DML: 갱신·삭제는 반드시 WHERE와 함께 — 먼저 SELECT로 대상 확인이 안전 습관
UPDATE complaint SET status = '완료' WHERE complaint_id = 1001;
DELETE FROM complaint WHERE status = '접수'
  AND created_at < datetime('now', '-2 years');

-- DDL: TRUNCATE는 전행 삭제 (DDL — 빠르지만 WHERE 불가)
TRUNCATE TABLE staging_complaint;
명령특징주의
DELETEDML. WHERE로 선택 삭제. 트리거·롤백 대상. 느림로그 남음 — 감사 필요 시
TRUNCATEDDL(구현별 상이). 전행 삭제·속도 빠름. WHERE 불가MySQL은 암시 커밋 / PostgreSQL은 롤백 가능
DROPDDL. 테이블 자체 삭제 — 구조·인덱스·데이터 전부복구 불가(백업 외)

4트랜잭션 — 문법부터 격리까지

"같이 성공하거나 같이 실패해야 하는 단위"가 트랜잭션 경계다. 민원 상태 전이(UPDATE + 이력 INSERT)가 전형적인 한 단위.

TCL 문법 — SAVEPOINT · 격리 수준 설정
BEGIN;                                   -- 트랜잭션 시작
UPDATE complaint SET status = '처리중' WHERE complaint_id = 1001;
INSERT INTO handling_log(complaint_id, actor, action)
  VALUES (1001, '박처리', '담당자 배정');
SAVEPOINT sp1;                           -- 부분 저장점
UPDATE complaint SET handler = NULL WHERE complaint_id = 1001;
ROLLBACK TO sp1;                         -- 저장점까지만 되돌림
COMMIT;                                  -- 전부 확정 (또는 ROLLBACK; 전체 폐기)

-- 격리 수준 설정
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;          -- PostgreSQL
SET SESSION transaction_isolation = 'READ-COMMITTED';    -- MySQL
격리 수준과 이상 현상 — 약→강 순
수준가시성비고
Read Uncommitteddirty read 가능 — 미커밋 데이터를 남이 읽음거의 사용 안 함
Read Committeddirty read 방지 · non-repeatable read 가능PostgreSQL 기본
Repeatable Readnon-repeatable read 방지 · phantom read 가능MySQL InnoDB 기본(실제로 phantom도 대부분 방어)
Serializable이상 전부 방지 — 직렬 실행과 동등 보장가장 안전·가장 느림

dirty read 타임라인 — 이상 현상은 '스케줄'로 설명하면 강하다

시각T1 (갱신)T2 (조회)
t1UPDATE status='처리중' (아직 미커밋)
t2SELECT → '처리중' 관찰 (dirty read)
t3ROLLBACK — 갱신 취소
t4T2가 본 '처리중'은 존재한 적 없는 값이 됨
  • 데드락 회피 — 여러 트랜잭션이 같은 행 집합을 건드릴 때 항상 같은 순서(예: PK 오름차순)로 접근
  • 트랜잭션은 짧게 — 사용자 입력·외부 API 호출을 트랜잭션 안에 넣지 않는다 (lock 장기화 = 병목)
  • MVCC — PostgreSQL·InnoDB·SQLite(WAL) 모두 읽기에 lock을 안 걸고 스냅샷을 읽는다. '읽기-쓰기 동시성'의 실체

5인덱스 튜닝 — 설계 · 실패 조건 · 측정

인덱스는 B-Tree 하나로 시작해도 결국 "조회 패턴을 보고 역방향으로 설계"하는 습관이 전부다. 개념(B-Tree·클러스터형)은 CS 백과 db 도메인 참조 — 여기는 실전.

복합 인덱스 순서 · 커버링 인덱스 · EXPLAIN
-- 복합 인덱스: 컬럼 순서가 곧 사용 가능성 (leftmost prefix)
CREATE INDEX idx_complaint_dept ON complaint(dept_id, created_at);
-- WHERE dept_id = 3                      → 인덱스 사용 O
-- WHERE dept_id = 3 AND created_at > ...  → 인덱스 사용 O (범위까지)
-- WHERE created_at > ...                 → 인덱스 사용 X (선행 컬럼 없음)

-- 커버링 인덱스: 조회 컬럼까지 인덱스에 담아 테이블 접근 자체를 제거
CREATE INDEX idx_complaint_dept_status ON complaint(dept_id, status);
SELECT status FROM complaint WHERE dept_id = 3;   -- 테이블 안 열어도 됨

-- 실행 계획 확인 — 튜닝은 측정에서
EXPLAIN QUERY PLAN
SELECT status FROM complaint WHERE dept_id = 3;
-- SQLite:  SEARCH complaint USING COVERING INDEX idx_complaint_dept_status
--          (vs SCAN = 전체 스캔, 수백만 행이면 경보)
-- PostgreSQL은 EXPLAIN ANALYZE — 실제 실행 시간까지
인덱스가 무시되는 5대 경우 — 각각의 해법까지
원인예시해법
컬럼 가공WHERE YEAR(created_at) = 2026범위 조건으로: created_at >= '2026-01-01' AND < '2027-01-01'
선행 % LIKEWHERE name LIKE '%소음%'접두 % 를 없애거나 전문 검색(FTS)으로 — 우리 코드의 FTS5가 그 답
암시적 형변환TEXT 컬럼에 WHERE phone = 1012345678타입 일치 — 문자열 리터럴로
OR 혼합WHERE dept_id = 3 OR memo = '긴급' (memo 무인덱스)UNION ALL 분리 또는 양쪽 인덱스
선택도 낮음성별 컬럼처럼 값 종류가 적은 조건플래너가 통계를 보고 포기 — 인덱스를 만들어도 안 탄다
  • 인덱스는 쓰기 비용 — INSERT/UPDATE마다 인덱스도 갱신. '일단 다 걸자'는 안티패턴
  • 조회 패턴(어떤 WHERE·ORDER BY가 자주 오는가)에서 역방향으로 인덱스를 설계한다
  • FK 컬럼 인덱스 — PostgreSQL·Oracle은 FK 생성 시 자동 인덱스가 없어 직접 생성, MySQL InnoDB는 자동 생성. 조인·ON DELETE 성능의 기본기

6우리 코드 연결 — 이 지식의 '실물'

면접의 승부처는 개념 암송이 아니라 "내가 만든 것으로 설명"이다. 아래는 전부 코드로 검증된 연결고리.

oxicode-mnemopi · oxibrain-store

SQLite + WAL 저널 — oxicode-mnemopi는 `session.rs:88`에서 journal_mode를 WAL로 설정

면접 화법TCL·WAL 이야기를 '내가 rusqlite로 직접 세팅한 경험'으로 말할 수 있다. 동시 reader + 단일 writer = MVCC 실현 사례

oxicode-mnemopi FTS5

역인덱스 + BM25 랭킹의 실물 — LIKE '%키워드%' 풀스캔을 회피하는 전문 검색의 정답

면접 화법'선행 % LIKE는 인덱스를 못 탄다'는 지식이 코드로 증명됨

oxibrain sqlite-vec

벡터 색인을 SQLite 안에 통합 — RDB 한 파일로 lexical(FTS5) + dense(sqlite-vec) 하이브리드 검색

면접 화법NoSQL/VectorDB 도입 없이 스키마·트랜잭션을 유지한 사례 — SI 면접에서 '신기술 남용 안 함' 논거

oximemo

redb 2(B-tree KV 저장소) + tantivy 0.22(역인덱스) — SQLite가 아니다

면접 화법용도별 저장소 선택 기준을 말할 수 있다. (정정 로그 2026-08-15 v2 참조)

how-many-blood

ASOS 일별 관측 × 헌혈 기록의 날짜 join·집계·파생 지표 파이프라인

면접 화법'데이터 결합·집계를 실제로 짜본 경험' — GROUP BY/JOIN 질문을 작업 스토리로 답한다

SI 면접 표준 답변 뼈대 — 이 페이지 전체의 요약
-- SI 면접 표준 답변 뼈대: "공공 시스템이라 정규화 3NF가 기본값,
-- 통계성 대시보드 조회가 heavy하면 측정 후 반정규화 요약 테이블을 예외로 둔다.
-- 트랜잭션 경계는 민원 상태 전이(UPDATE + 이력 INSERT)처럼
-- '같이 성공하거나 같이 실패해야 하는 단위'로 설계한다."
-- → 개념 암송이 아니라 판단 기준을 말하는 것 — 이 페이지 전체의 요약.

7면접 빠른 답 — 질문 받으면 이 순서로

Q정규화가 왜 필요한가요?

갱신 이상(삽입·갱신·삭제)을 제거해 데이터가 스스로 일관되게 유지되게 하려고. 예: 부서 이전 시 도시과 민원 행 전부를 갱신하는 실수를 구조적으로 원천 차단. 실무는 3NF 기본 + 측정 후 반정규화 예외.

QWHERE와 HAVING의 차이는?

실행 순서. WHERE는 그룹화 이전 개별 행을 걸러내고, HAVING은 GROUP BY 이후 그룹별 집계 결과를 걸러낸다. 그래서 집계 함수 조건은 HAVING에만 올 수 있다.

QDELETE · TRUNCATE · DROP의 차이는?

DELETE는 DML — WHERE 선택 삭제·롤백·트리거 대상. TRUNCATE는 전행 삭제 DDL — 빠르고 WHERE 불가, 트랜잭션 소속은 구현별(MySQL 암시 커밋 / PostgreSQL 롤백 가능). DROP은 테이블 자체 제거.

Q인덱스는 언제 안 타나요?

컬럼을 가공했을 때(함수·연산), LIKE가 선행 %로 시작할 때, 암시적 형변환이 일어날 때, OR로 무인덱스 조건이 섞였을 때, 통계상 선택도가 낮아 플래너가 포기할 때. 해법은 항상 '조건을 인덱스 친화적으로 다시 쓰기'다.

QPK와 UNIQUE의 차이는?

PK는 테이블당 1개·NULL 불가. UNIQUE는 여러 개 가능하고 표준상 NULL을 허용한다(구현별로 다중 NULL 처리 상이). 둘 다 B-Tree 인덱스가 생성된다.

Q격리 수준과 각 수준에서 생기는 이상 현상은?

Read Uncommitted — dirty read / Read Committed — non-repeatable read / Repeatable Read — phantom read / Serializable — 전부 방지. PostgreSQL 기본은 RC, MySQL은 RR. 실제 구현은 MVCC 스냅샷 기반이라 표보다 강하다.

QN+1 문제가 뭐고 어떻게 해결하나요?

목록 1번 쿼리 + 각 행의 연관 데이터를 행마다 다시 조회해 N+1번 실행되는 패턴 — 챗봇·SI 연동 코드에서 최빈 성능 버그. JOIN으로 한 번에 가져오거나 IN (…) 배치 조회로 묶는다. ORM 기본 동작이 N+1인 경우가 많아 발견이 중요하다.

더 깊은 개념(MVCC 내부·WAL 구조·B-Tree 분할·벡터 인덱스)은 CS 백과 데이터베이스 도메인의 13개 용어 카드와 양방향으로 연결해 읽는다.