데이터를 행과 열로 구조화하여 저장
원리
사용 이유 / 사용 시점 : 정형화된 데이터 저장, 구조화된 검색 필요할 때
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100) UNIQUE, created_at TIMESTAMP );
PK(Primary Key)
id INT PRIMARY KEY
FK(Foreign Key)
user_id INT, FOREIGN KEY (user_id) REFERENCES users(id)
제약조건
무결성(Integrity)
데이터의 정확성과 일관성이 항상 유지되는 성질
종류 :
개체 무결성(Entity Integrity)
참조 무결성(Referential Integrity)
도메인 무결성(Domain Integrity)
사용자 정의 무결성(User-defined Integrity)
중복 데이터 제거 및 이상현상(Anomaly) 방지를 위해 테이블 구조 최적화
원리 :
다치 종속 제거
조인 종속 제거
사용 이유 / 사용 시점 : 데이터 무결성 확보, 유지보수 용이
장점 :
단점 :
반정규화(Denormalization)
정규화로 나눈 테이블을 일부러 합치거나 중복 데이터를 허용하여, 조회 성능을 높이는 설계 방법
JOIN 횟수를 줄여 조회 속도를 개선하고, 일부 컬럼 중복 허용을 통해 조회에 피룡한 데이터를 한 번에 가져옴
SELECT
SELECT name, email FROM users WHERE id = 1;
JOIN
여러 테이블 연결
사용 시점 :
종류 :
SELECT u.name, d.department_name FROM users u JOIN departments d ON u.department_id = d.id;
서브쿼리(Subquery)
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE price>100);
검색 속도 향상을 위한 데이터 구조
특정 컬럼의 값을 빠르게 찾기 위해 미리 정렬/구조화
원리 :
B+Tree Index :
균형 잡힌 다중 자식 트리
정렬된 트리 구조 -> 범위 검색 O(log n)
범위 검색과 정렬된 데이터 빠른 탐색
특징 :
B-Tree와 차이점 : B-Tree는 내부노드에 탐색용 키, 실제 데이터가 저장될 수 있음
Hash Index :
Composite Index :
Unique Index :
쓰기 성능 저하 이유
INSERT 시점
UPDATE 시점
DELETE 시점
사용 이유 / 사용 시점 : 조회 빈도가 높고 조건절이 많은 컬럼
CREATE INDEX idx_user_email ON users(email);
BEGIN TRANSACTION; UPDATE accounts SET balance=balance-100 WHERE id=1; UPDATE accounts SET balance=balance+100 WHERE id=2; COMMIT;
고립 단계(Isolation Level)
고립 : 한 트랜잭션이 처리 중인 데이터가 다른 트랜잭션에 의해 영향을 받지 않도록 하는 정도
여러 트랜잭션이 동시에 실행될 때 데이터 일관성을 보장하기 위한 수준 정의
단계 :
READ UNCOMMITED :
Dirty Read : 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 것READ COMMITED :
REPEATABLE READ :
Non-Repeatable Read(반복 불가능한 읽기) : 같은 트랜잭션이 같은 데이터를 두 번 읽었는데, 중간에 다른 트랜잭션이 수정해서 값이 달라지는 현상SERIALIZABLE :
Phantom Read : 트랜잭션이 같은 조건으로 데이터를 두 번 조회했는데, 중간에 다른 트랜잭션이 새로운 행을 삽입/삭제해서 결과가 달라지는 현상교착상태(Deadlock)
Not Only SQL
전통적 관계형 DB(RDBMS)와 달리 스키마 고정 없이 데이터 저장/조회 가능
대규모, 분산 환경에서 성능과 확장성을 중점적으로 설계
특징: 유연한 데이터 모델, 수평 확장성, 빠른 조회/쓰기
종류 :
Key-Value Store
Document Store
Column Family Store
Graph DB
DB + 캐시조합 : 캐시는 메모리 기반으로 빠른 조회, DB(RDBMS)는 영속성을 보장하므로 둘 조합시 실시간 데이터 처리 및 안정성 확보 가능
인메모리 키-값 구조 DB
메모리(RAM, 휘발성 메모리)에 데이터 저장 -> 서버가 꺼지거나 재시작하면 기본적으로 메모리 데이터는 사라짐
필요시 디스크에 영속성 저장(AOF, RDB) 옵션 설정 가능
원리 :
장점 :
단점 :
TTL(Time To Live)
사용 이유 / 사용 시점 : 캐싱, 세션관리, 실시간 통계
// Java + Jedis 사용 예시 Jedis jedis = new Jedis("localhost"); // 캐시 저장 jedis.set("user:1001", "{'name':'홍길동','age':25}"); jedis.expire("user:1001", 3600); // 1시간 후 만료 // 캐시 조회 String userData = jedis.get("user:1001");
Key로 Value조회
원리 : 해시테이블 또는 트리 기반
사용 이유 / 사용 시점 : 빠른 조회 필요, 단순 데이터 저장
{ "user:1": {"name": "Alice", "age": 30} }
Java와 DB 연결 표준 API
원리 : DriverManager -> Connection -> Statement/PreparedStatement -> ResultSet
PreparedStatement
// Statement (취약 예시) Statement stmt = conn.createStatement(); String userId = "1001 OR 1=1"; // SQL Injection 위험 ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE id=" + userId); // PreparedStatement (안전) String sql = "SELECT * FROM users WHERE id = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 1001); // 파라미터 바인딩 ResultSet rs2 = pstmt.executeQuery();
Connection Pool
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb"); config.setUsername("user"); config.setPassword("pass"); config.setMaximumPoolSize(10); // 최대 10개 연결 HikariDataSource ds = new HikariDataSource(config); // 커넥션 사용 try (Connection conn = ds.getConnection()) { PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE id=?"); pstmt.setInt(1, 1001); ResultSet rs = pstmt.executeQuery(); // 결과 처리 }
Connection conn = DriverManager.getConnection(url, user, pwd); PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?"); ps.setInt(1, 1); ResultSet rs = ps.executeQuery();
DB 접근 추상화, 도메인 중심 개발
원리 : DAO(Data Access Object)개선, 인터페이스 기반
public interface UserRepository extends JpaRepository<User, Integer> { List<User> findByName(String name); }
Lazy Loading : 필요한 시점에 데이터 조회 -> 초기 로딩 가벼움
Eager Loading : 미리 JOIN하여 모두 조회 -> 한번에 처리, 초기 비용 높음
N+1 문제 : Lazy 로딩 반복 -> 쿼리 N+1번 발생
예시 : Order 10개 조회시 -> Users 1번 쿼리 + Orders 10번 쿼리 = 총 11번 쿼리
@OneToMany(fetch = FetchType.LAZY) private List<Order> orders;