[CS 면접 질문] 데이터베이스
SQL vs NoSQL
SQL (관계형 DB)
SQL을 사용하면 RDBMS에서 데이터를 저장, 수정, 삭제 및 검색을 할 수 있다. 데이터는 정해진 스키마에 따라 저장되고, 관계를 통해 여러 테이블에 분산된다.
스키마를 준수하지 않는 데이터는 추가할 수 없으며, 정규화를 통해 데이터들을 여러 테이블에 나누어 저장하기 때문에 데이터의 정합성을 보장할 수 있다.
NoSQL
NoSQL은 RDB와 반대로 정해진 스키마도, 관계도 없다. Key-Value로 데이터(문서)가 저장되며 다른 구조의 데이터를 같은 컬렉션에 추가할 수 있다. RDB처럼 여러 테이블에 나누어담지 않고, 관련 데이터를 동일한 컬렉션에 넣는다.
SQL과 NoSQL의 장단점
- SQL 장점
- 스키마가 명확하게 정의되어 있어 데이터 무결성이 보장됨
- 관계를 설정하기에, 데이터는 중복없이 한번만 저장됨
- SQL 단점
- 유연하지 못함. 스키마를 사전에 계획해 알려야하고 나중에 수정이 어려움
- 조인을 많이 해야할 경우 복잡한 쿼리가 만들어질 수 있음
- 대체로 수직적 확장만 가능 (서버 성능 업그레이드)
- NoSQL 장점
- 스키마가 없어서 유연함. 언제든지 저장된 데이터를 조정하고 새로운 필드 추가 가능
- 애플리케이션이 필요로 하는 형식으로 데이터를 저장하기 때문에 읽어오는 속도가 빨라짐
- 수직 및 수평(서버 추가) 확장 모두 가능해서 애플리케이션이 발생시키는 모든 읽기/쓰기 요청 처리 가능
- NoSQL 단점
- 유연성으로 인해 데이터 구조 결정을 계속 미루게 될 수 있음
- 데이터가 여러 컬렉션에 중복되어 있기 때문에 업데이트 시 모든 컬렉션마다 업데이트 필요
언제 어떤걸 쓰면 좋을까?
RDB는 변경될 여지가 없고, 명확한 스키마가 중요한 경우 사용하는 것이 좋다. 또한 중복된 데이터가 없어(데이터 무결성) 변경이 용이하기 때문에 관계를 맺고 있는 데이터가 자주 변경되는 시스템에 적합하다.
NoSQL은 정확한 데이터 구조를 알 수 없거나 데이터가 변경/확장 될 수 있는 경우 사용하는 것이 좋다. 또한 읽기를 자주 하지만, 데이터 변경은 자주 없는 경우 유리하다.
ORM이란?
ORM(Object Relation Mapping)이란 객체와 RDB를 매핑시켜 테이블을 객체지향적으로 사용하게 해주는 기술이다. Java에서 사용하는 대표적인 ORM으로는 JPA와 그의 구현체 Hiberante가 있다.
ORM을 쓰는 이유
객체 지향적인 코드로 인해 더 직관적이고, 비즈니스 로직에 집중할 수 있다. 또한 DBMS에 대한 종속성이 줄어들기 때문에 유지보수성이 높다.
Index
데이터베이스 테이블의 검색 속도를 향상시키기 위해 사용하는 기술이다. 테이블을 처음부터 끝까지 풀스캔하는 것이 아닌, 인덱스 정보가 저장된 트리 구조의 파일을 검색하고 검색된 인덱스 값으로 원본 테이블을 액세스하여 데이터를 조회한다.
Index는 어떻게 동작할까
초기 테이블 생성 시 3개의 파일이 생성된다.
- .frm : 테이블 구조 저장
- .myd : 실제 데이터 저장
- .myi : index 정보 저장
생성된 파일(.myi)에 해당 컬럼이 색인화하여 저장된다.
사용자가 쿼리를 통해 인덱스를 사용하는 컬럼을 검색하면 myi 파일의 내용을 검색한다.
검색된 인덱스 값으로 원본 테이블에서 값을 조회한다.
DBMS가 Index를 관리하는 방법
B+Tree 자료구조
자식 노드가 2개 이상인 B-Tree를 개선한 자료구조로, B-Tree 리프노드들을 LinkedList로 연결하여 순차 검색을 용이하게 한다. 해시 테이블보다 느린 O(logN)의 시간복잡도를 갖지만 일반적으로 사용된다.해시테이블 자료구조
컬럼의 값으로 생성된 해시를 기반으로 인덱스를 구현한다. 시간복잡도가 O(1)로 매우 빠른 검색을 지원한다.
왜 더 빠른 해시테이블이 아닌 B+Tree를 일반적으로 사용하는걸까?
해시테이블의 상수 시간복잡도 O(1)
은 동등 연산(=
)인 경우에 보장된다. 그러나 질의 조건에는 부등호(<
, >
)나 BETWEEN
과 같은 범위 연산이 포함되기도 한다. 해시테이블은 값이 정렬되어 있지 않아 부등호 연산과 같은 연속적인 데이터를 위한 순차 검색이 불가능하여 적합하지 않다.
Index를 사용하면 반드시 빠를까?
인덱스는 항상 정렬된 상태를 유지하기 때문에 원하는 값의 검색과 정렬 속도를 향상시킬 수 있다. 또한 테이블 조인 시 인덱스된 필드를 사용하면 성능을 높일 수 있다.
하지만 인덱스된 필드에 대해 삽입, 삭제, 수정이 빈번히 발생한다면 인덱스 테이블 또한 재정렬이 필요하기 때문에 전체적인 성능 저하가 발생할 수 있다. 또한 인덱스를 생성할수록 데이터베이스에 추가 공간이 필요하며, 유니크하지 않은 필드를 인덱스로 생성하면 성능이 향상되지 않는다.
Index를 사용하면 좋은 경우
- WHERE 절에서 자주 사용되는 컬럼
- 외래키가 사용되는 컬럼
- 조인에 자주 사용되는 컬럼
Index 사용을 피해야하는 경우
- 데이터 중복이 높은 컬럼
- 삽입, 삭제, 수정이 빈번한 컬럼
트랜잭션
데이터베이스의 상태를 변화시키기 위해 수행하는 작업 단위로, 작업의 완전성을 보장한다. 작업들을 모두 처리하거나(COMMIT), 처리하지 못할 경우 이전 상태로 복구(ROLLBACK)하여 작업의 일부만 적용되는 현상이 발생하지 않게 한다.
트랜잭션의 특성(ACID)
- 원자성(Atomicity) : 작업이 모두 반영되거나, 전혀 반영되지 않아야 한다.
- 일관성(Consistency) : 작업 처리 결과는 항상 일관되게 유지되어야 한다.
- 독립성(Isolation) : 각각의 트랜잭션은 서로 독립적으로 동작하고 영향을 주지 않아야 한다.
- 영속성(Durability) : 트랜잭션이 완료된 이후에는 영구적으로 반영되어야 한다
트랜잭션 격리 수준
여러 트랜잭션이 동시에 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 여부를 결정하는 것이다. 격리 수준이 높은 순서대로 SERIALIZABLE, REPEATABLE READ, READ COMMITTED, READ UNCOMMITTED가 있다.
- SERIALIZABLE : 트랜잭션이 읽은 데이터를 다른 트랜잭션이 갱신, 삭제, 삽입할 수 없다. 트랜잭션이 순차적으로 처리되어야 하므로 동시 처리 성능이 매우 떨어진다.
- REPEATABLE READ : 트랜잭션이 읽은 데이터를 다른 트랜잭션이 갱신, 삭제할 수 없다. 삽입을 막지 않기 때문에 조회시 트랜잭션이 끝나기 전에 다른 트랜잭션에 의해 추가된 레코드가 발견될 수 있다.1
- READ COMMITTED : COMMIT된 데이터만 다른 트랜잭션이 조회할 수 있다. 가장 일반적으로 선택되는 격리 수준이다.
- READ UNCOMMITTED : COMMIT 여부와 상관없이 다른 트랜잭션이 데이터를 조회할 수 있다. 동시성은 높지만 데이터의 일관성을 유지하기 어렵다.
락
트랜잭션이 처리되는 순서를 보장하기 위한 방법이다. 대표적으로 데이터의 무결성을 유지하기 위한 공유 락과 베타 락이 있다.
- 공유 락 (Shared Lock) : 트랜잭션이 읽기를 할 때 사용하는 락으로, 같은 공유 락 끼리는 데이터에 동시에 접근할 수 있다.
- 베타 락 (Exclusive Lock) : 데이터를 변경할 때 사용하는 락으로, 하나의 베타 락이 데이터에 접근 중일 때 다른 베타 락이 접근할 수 없다.
교착 상태 (DeadLock)
한 트랜잭션이 처리 중인 자원에 대해 락을 가진 상태에서 다른 트랜잭션이 처리 중인 데이터에 대해 락을 요청하면 아무리 기다려도 상황이 바뀌지 않는 상태가 되는데, 이를 교착상태라고 한다.
교착 상태, 어떻게 해결할까
- 예방 기법 : 각 트랜잭션이 실행되기 전에 필요한 모든 자원에 대해 미리 락을 얻는다. 병행성이 떨어지는 단점이 있다.
- 회피 기법 : 자원을 할당할 때 시간 스탬프(Time Stamp)를 활용해서 교착 상태를 회피하는 방식이다.
- Wait-Die 방식 : 트랜잭션 A가 트랜잭션 B에 의해 잠금된 데이터를 요청할 때, A가 먼저 들어온 트랜잭션이라면 대기(Wait)한다. 만약 A가 나중에 들어온 트랜잭션이라면 포기(Die)하고 나중에 다시 요청한다.
- Wound-Wait 방식 : 트랜잭션 A가 먼저 들어온 트랜잭션이라면 데이터를 선점(Wound)한다. A가 나중에 들어온 트랜잭션이라면 대기(Wait)한다.
정규화(Normalization)
관계형 데이터베이스에서 중복을 최소화하기 위해 릴레이션을 분해하는 과정이다. 중복된 데이터를 허용하지 않음으로써 무결성(Integrity)를 유지할 수 있다.
정규화가 필요한 이유
하나의 테이블에 모든 정보를 다 넣게 된다면, 동일한 정보들이 불필요하게 중복되어 저장될 수 있다. 또한 중복된 정보로 인해 이상 현상(Anomaly)이 발생하여 어느 것이 정확한지 알 수 없게 되는데, 이러한 문제를 해결하기 위해 정규화 과정이 필요하다.
즉, 데이터 중복 최소화와 이상 현상 방지를 위해 정규화 과정을 거친다.
이상 현상(Anomaly)이란
테이블을 설계할 때 잘못 설계하여 데이터를 삽입, 삭제, 수정할 때 생기는 논리적 오류
- 삽입 이상 : 데이터를 삽입할 때 특정 속성에 해당하는 값이 없어 NULL을 입력해야 하는 현상
- 수정 이상 : 데이터 수정 시, 중복된 데이터 중 일부만 수정되어 데이터 모순이 일어나는 현상
- 삭제 이상 : 데이터를 삭제하면 의도하지 않은 다른 정보까지 연쇄적으로 삭제되는 현상
정규화의 과정
정규화에는 여러가지 단계가 있지만, 대체적으로 1~3단계 정규화까지의 과정을 거친다.
- 제 1정규화 (1NF) : 테이블의 칼럼이 원자 값을 갖도록 테이블을 분리한다.
- 제 2정규화 (2NF) : 제 1정규화를 만족하고, 기본키가 아닌 속성이 기본키에 완전 함수 종속2이도록 분리한다.
- 제 3정규화 (3NF) : 제2 정규형을 만족하고, 이행적 함수 종속3을 없애도록 분리한다.
- BCNF 정규화 : 제3 정규형을 만족하고, 함수 종속성 X → Y가 성립할 때 모든 결정자 X가 후보키가 되도록 분리한다.
정규화를 하면 어떤 장점이 있을까
- 데이터베이스 변경 시 이상현상이 발생하는 문제점을 해결할 수 있다.
- 데이터베이스 구조 확장 시, 정규화된 데이터베이스는 그 구조를 변경하지 않아도 되거나 일부만 변경해도 된다.
정규화의 단점은?
릴레이션의 분해로 인해 릴레이션 간의 연산(JOIN)이 많아진다. 이로 인해 질의에 대한 응답 시간이 느려질 수 있는데, 이처럼 성능저하가 나타나는 경우에 반정규화를 적용하는 전략이 필요하다.
+) 정규화를 수행한다는 것은 이상 현상을 제거하는 것이다. 데이터의 중복 속성을 제거하고 결정자에 의해 동일한 의미의 일반 속성이 하나의 테이블로 집약되므로 한 테이블의 데이터 용량이 최소화되는 효과가 있다. 따라서 정규화된 테이블은 데이터를 처리할 때 속도가 빨라질 수도 있고 느려질 수도 있는 특성이 있다.
반정규화
반정규화는 정규화된 엔티티, 속성, 관계를 성능 향상 또는 개발과 운영의 단순화를 위해 중복 통합, 분리 등을 수행하는 과정이다. 일반적으로 조회 작업에서 JOIN 연산이 많아 성능 저하가 예상되는 경우 반정규화를 수행하게 된다.
참고 자료
Interview_Question_for_Beginner
tech-interview-for-developer
신입 개발자 기술면접 질문 정리 - 데이터베이스
망나니 개발자님 블로그