-
[6] 데이터베이스 물리적 모델링프로젝트/대출금 상환 프로젝트 2026. 6. 14. 14:13
들어가며
지금까지 대출금 상환 프로젝트에 대해 개념적 , 논리적 데이터베이스 모델링을 진행했습니다.
회원 , 대출계약 , 대출계좌 , 상환 스케줄 , 연체 이력 등 15 개 테이블과 그 관계를 정의하고 정규화 과정 까지 마쳤습니다.
아래는 그 결과 ERD 입니다.

이번 글에서는 논리적 모델링 결과를 바탕으로 물리적 모델링을 진행하겠습니다.
물리적 모델링은 ERD 를 바탕으로 MySQL 이라는 구체적인 DBMS 에 최적화된 형태로 구현하는 과정 입니다.
진행 순서
물리적 모델링은 다음 3단계로 진행 합니다.
- 테이블과 컬럼 변환: 논리적 모델의 한글 엔티티명/속성명을 명명 규칙(Naming Convention)에 따라 영문으로 변환합니다.
- 데이터 타입 정의: 각 컬럼에 가장 적합한 자료형(VARCHAR, BIGINT, DECIMAL 등)을 선택합니다. 저장 공간과 성능에 직접적인 영향을 미치는 작업입니다.
- 제약조건 설정: 기본 키, 외래 키, NOT NULL, UNIQUE 등 데이터 무결성을 보장하기 위한 제약조건을 구체적으로 설정합니다.
물리적 모델링 결과
물리적 모델링을 마치면 2가지 산출물이 나옵니다.
- 테이블 정의서: 각 테이블과 컬럼이 무엇을 의미하고 어떤 규칙을 가지는지 상세히 기술한 설계 명세서입니다.
- DDL 스크립트: 실제로 CREATE TABLE을 실행해서 테이블을 생성할 수 있는 SQL 스크립트입니다.
코드성 테이블
코드성 테이블(이자종류, 상환방법, 대출상태, 상환스케줄상태, 연체이력상태 , 연체근거)은 모두 동일한 패턴을 가집니다.

PK 가 INT
항목 수가 명확히 제한된 코드성 데이터라 BIGINT 가 불필요 하다고 생각했습니다.
컬럼명이 name
테이블명이 이미 컨텍스트를 주므로 (interest_type 안의 name 은 당연히 이자종류명) interest_type_name 처럼 접두사를 붙이지 않습니다.
1. 이자종류 (interest_type)
고정금리, 변동금리와 같은 대출계약에 적용되는 이자 산정 방식의 종류를 관리합니다.
2. 상환방법 (repayment_method)
원리금균등상환, 원금균등상환, 만기일시상환와 같은 대출상품이 지원하는 상환 방식을 관리합니다.
3. 대출상태 (loan_status)
대출계좌의 현재 상태를 관리합니다. 대출상태 이력을 통해 시점별 변화를 추적합니다.
4. 상환스케줄상태 (repayment_schedule_status)
상환 스케줄 한 회차가 현재 어떤 상태인지를 관리합니다. 상환 스케줄이 참조합니다.
5. 연체이력상태 (delinquency_history_status)
연체 이력 한 건이 현재 진행 중인지, 해소되었는지 등을 관리합니다. 연체 이력이 참조합니다.
6. 연체근거 (delinquency_reason)
연체 이력 한 건에 대해 연체 근거를 관리합니다. 연체 이력이 참조합니다.
핵심 테이블
1. 회원 (member)

member_id BIGINT
회원은 계속 누적되는 핵심 데이터입니다. 서비스를 지속하면서 계속 늘어나는 데이터 이므로 BIGINT 를 사용하였습니다.
가입일시 → created_at
ERD의 가입일시는 "회원이 가입한 시점"이라는 비즈니스 이벤트입니다. 명명 규칙상 이벤트 발생 시점은 _at 접미사를 쓰고, 이는 곧 "데이터가 처음 생성된 시점"과 동일하므로 별도 컬럼 없이 공통 컬럼 created_at으로 흡수합니다.
password VARCHAR(255)
평문이 아닌 해시값(BCrypt 등)이 저장되므로, 원본 비밀번호 길이보다 해시 결과 길이를 고려해 충분히 여유 있게 잡았습니다.
2. 주 계좌 (primary_account)

member_id에 UQ
ERD상 회원:주계좌가 1:1 관계 입니다. FK에 UNIQUE를 걸어 "한 회원은 주 계좌를 하나를 반드시 가진다"는 제약을 DB 레벨에서 강제합니다.
계좌번호 → account_no
보편적인 약어 활용을 하여 number → no로 축약했습니다.
잔액 → DECIMAL(23,4)
금융 데이터는 부동소수점(FLOAT/DOUBLE) 사용 시 미세한 오차가 발생할 수 있어 절대 사용하지 않습니다. DECIMAL(23,4)로 정수부 19자리, 소수부 4자리까지 정확하게 저장합니다.
balance DEFAULT 0
계좌 생성 시점에 잔액이 0원인 경우가 일반적이므로 기본값을 명시했습니다.
3. 대출상품 (loan_product)

FK interest_type_id, repayment_method_id 타입 일치
FK는 참조하는 PK와 데이터 타입이 반드시 같아야 합니다. 이자종류, 상환방법의 PK가 INT이므로 여기서도 INT를 사용합니다.
가산금리 → DECIMAL(10,5)
기준금리 + 가산금리 - 우대금리 구조에서 가산금리는 2.50000%처럼 소수점 5자리까지의 정밀도가 필요할 수 있습니다. DECIMAL(10,5)는 정수부 5자리, 소수부 5자리까지 — 최대 99999.99999까지 표현 가능해 여유 있게 설정했습니다. 이후 등장할 모든 금리(적용금리, 연체이자율 등) 컬럼에도 동일한 DECIMAL(10,5)를 적용합니다.
운영 테이블
1. 대출계약 (loan_contract)

계약일/만기일 → _date
시각 정보가 의미 없는 날짜 전용 데이터입니다. 명명 규칙상 _date 접미사를 사용합니다.
계약대출원금 → contract_principal
"계약 당시 확정된 대출 원금"이라는 의미입니다.
ERD에서 대출계좌의 대출원금잔액(현재 남은 금액, 변동값)과 구분하기 위해 _principal로만 명명하고 balance를 붙이지 않았습니다.
계약 시점의 고정값이라는 의미를 살린 것입니다.
FK member_id에는 UQ 없음
주계좌와 달리 대출계약은 ERD상 회원:계약이 1:N입니다.
한 회원이 여러 계약을 가질 수 있으므로 UNIQUE 제약을 걸지 않습니다.
2. 대출계좌 (loan_account)

loan_contract_id에 UQ
ERD상 대출계약:대출계좌가 1:1 관계입니다. "한 계약은 하나의 대출계좌만 가진다"를 DB 레벨에서 강제합니다.
대출원금잔액 → loan_principal_balance
대출계약.contract_principal(계약 시점의 고정 원금)과 달리, 상환이 진행되면서 계속 줄어드는 현재 잔액입니다.
그래서 _balance 접미사를 붙여 변동값임을 명확히 했습니다.
매달 상환일 → monthly_payment_day TINYINT
매달 1~31일 중 한 값만 가지므로 TINYINT(−128~127)로 충분합니다. INT를 쓰면 4바이트가 낭비됩니다.
현재 회차 → current_installment_no INT
보편적인 약어 활용에 따라 number → no로 축약했습니다.
회차는 보통 12회, 36회 등 INT 범위 내에서 충분합니다.
FK primary_account_id 타입
주계좌의 PK가 BIGINT이므로 동일하게 BIGINT입니다.
3. 대출상태 이력 (loan_status_history)

PK가 BIGINT
이력 테이블은 시간이 지날수록 데이터가 계속 누적되는 대표적인 테이블입니다.
처음엔 적어 보여도 장기적으로 가장 빠르게 row 수가 늘어날 수 있어 BIGINT를 채택했습니다.
종료일 → end_date NULL 허용
현재 진행 중인 상태는 종료일이 정해지지 않았습니다. "현재 상태"는 end_date IS NULL인 행으로 식별합니다.
loan_account_id에 UQ 없음
한 대출계좌는 여러 개의 상태 이력(정상→연체→정상...)을 가질 수 있으므로(1:N) UNIQUE를 걸지 않습니다.
상환 테이블
1. 상환 스케줄 (repayment_schedule)

예정일 → scheduled_at
대출계좌 생성 시점에 1회차~N회차 일정이 한 번에 확정되고 이후 변경되지 않는 값이므로, "변경 가능"이라는 의미를 가진 _dt는 맞지 않습니다.
반면 _at은 "특정 작업이 발생한 정확한 시점"을 의미하며, "1회차 상환은 2026-07-15에 발생하기로 확정되었다"는 확정된 사건의 시점으로 해석할 수 있어 _at을 사용했습니다.
상환금(원금,이자) → 컬럼 분리
ERD에는 상환금 (원금,이자) 하나로 표기되어 있지만, 물리 모델에서는 scheduled_principal, scheduled_interest 두 컬럼으로 분리했습니다.
원금과 이자는 회계적으로 성격이 다른 값이고, 변제충당순서(이자 우선 vs 원금 우선) 로직에서 각각 독립적으로 다뤄야 합니다.
합산값 하나만 저장하면 상환이력에서 "원금 얼마/이자 얼마 납부했는지"를 계산하기 어렵습니다.
적용금리 → applied_rate DECIMAL(10,5)
이자율은 4.50000%처럼 소수점 5자리까지의 정밀도가 필요합니다.
DECIMAL(10,5)는 정수부 5자리, 소수부 5자리까지 표현 가능해 충분한 여유를 둡니다.
2. 상환 이력 (repayment_history)

납부 일시 → paid_at
"회원이 납부했다"는 이미 발생한 비즈니스 이벤트이므로 _at을 사용합니다.
created_at(이 row가 DB에 생성된 시점)과는 의미가 다릅니다.
배치가 다음날 새벽에 상환이력을 생성한다면, paid_at은 실제 납부 시각, created_at은 배치가 row를 만든 시각으로 구분됩니다.
delinquency_history_id → NULL 허용
ERD상 상환이력:연체이력은 선택적 관계 입니다. 정상 상환은 연체와 무관하므로 FK 값이 없을 수 있습니다.
납부 연체금 → paid_delinquency_charge DEFAULT 0
연체 없는 정상 상환에서는 0원이 들어가는 것이 자연스러우므로 기본값을 0으로 설정했습니다.
3. 연체 이력 (delinquency_history)

연체 일시 → delinquent_at
"연체가 발생했다"는 이미 일어난 이벤트이므로 _at을 사용합니다.
근거id → delinquency_reason_id, NOT NULL
연체 이력이 생성되었다면 그 발생 사유는 반드시 존재하는 정보이므로, 코드성 테이블 delinquency_reason을 참조하는 FK를 NOT NULL로 설정했습니다.
연체 이자율 → delinquent_rate DECIMAL(10,5)
상환 스케줄.applied_rate와 동일한 정밀도가 필요합니다. 연체이자율은 보통 정상 적용금리 + 가산율로 계산되기 때문입니다.
repayment_schedule_id에 UQ 없음
ERD상 상환스케줄:연체이력은 1:N 입니다. 한 회차가 여러 일에 걸쳐 연체를 할 수 있으므로 UNIQUE를 걸지 않았습니다.
마무리 — 테이블 정의서와 DDL
논리적 모델링에서 정의한 15개 엔티티를 명명 규칙에 따라 영문 테이블/컬럼으로 변환하고, 데이터 타입과 제약조건까지 결정했습니다.
그 결과물로 두 가지를 정리했습니다.
1. 테이블 정의서
테이블 정의서는 DDL만으로는 알 수 없는 정보를 담는 문서입니다.
[링크]
1. DDL 스크립트
실제로 CREATE TABLE을 실행해서 테이블을 생성할 수 있는 SQL 스크립트입니다.
[링크]
'프로젝트 > 대출금 상환 프로젝트' 카테고리의 다른 글
은행 아키텍처 알아보기 (0) 2026.06.21 [5] 데이터베이스 논리적 모델링2 - 정규화 (0) 2026.06.06 [4] 데이터베이스 논리적 모델링1 - 테이블 설계 (0) 2026.06.03 [3] 데이터베이스 개념적 모델링 (0) 2026.05.31 [2] 가계 대출 상품 설명서 분석 - 2 (0) 2026.05.17 [2] 가계 대출 상품 설명서 분석 - MVP 기능 (0) 2026.05.17 [4] 대출금 상환 프로그램 설계 (0) 2026.05.04 [1] 프로젝트 개요 (0) 2026.05.02