데이터베이스 정리
한페이지에 데이터베이스의 핵심내용을 정리한다.
데이터베이스란?
데이터베이스란 데이터들의 집합이다. 별도의 데이터베이스 관리 시스템을 두지 않는다면 데이터 무결성(유효성), 중복성 문제가 발생한다. 이를 위해 데이터베이스를 관리하는 시스템을 만들게 된다.
DBMS란? 데이터베이스를 관리하며, CRUD 기능과 동시성 문제를 처리하는 시스템이며 아래와 같은 특징을 가진다.
- 독립성(응용과 독립)
- 무결성(유효한 데이터)
- 보안성(접근 제어)
- 일관성
I/O 효율
데이터베이스는 하드디스크에 데이터를 저장하기에, 접근하는 것이 매우 느리다. 그렇기에 최대한 더 효율적으로 데이터를 가져와야 한다. “튜닝”이 중요할 수 밖에 없다. 또한, 수직적으로 확장하는 데에는 무리가 있다. 240GB보다 60GB * 4개의 디스크가 금액적으로 유리할 가능성이 크다. 또한, 240GB를 저장할 수 있더라도 I/O가 60GB의 디스크보다 월등하지 않다면, 60GB의 디스크는 분산처리가 가능하므로 비효율적이다. 관련된 내용은 샤딩 파트에서 확인할 수 있다.
- 우리에겐 물탱크가 큰 물탱크 한개와 작은 물탱크 2개가 있어, 우리는 파이프를 통해 물탱크의 물을 빼거나, 넣는데 파이프는 모든 물탱크가 동일해. 그러면 우리가 한번에 많은 물을 빼고 싶어, 큰 물탱크에서 빼는 시간보다 작은 물탱크 2개에서 빼는 시간이 더 적겠지? 파이프가 같으니까. 그렇기에 우리는 큰 디스크를 두는 게 아니라, 작은 디스크를 여러 개두어서 데이터를 분산처리해, I/O 효율을 높일 수 있지
인덱스
데이터를 미리 확인하여, 데이터 검색 속도를 높인다. B-Tree 구조이며, 이는 BST 중 하나이다. 데이터를 생성 / 삭제하면 인덱스를 또 처리해야 하므로, DML 작업이 많으면 비용이 크다. 하지만 단순 조회만 많이한다면 검색 속도를 $O(log)$로 줄일 수 있으니 매우 효율적이다. 하지만, 만약 데이터의 중복이 많다면 인덱스를 사용하는 것을 지양해야 한다. 만약 ‘성별’로 인덱싱을 진행한다면 별의미가 없다.
장점
select 검색 속도를 크게 향상 시킨다. ex) select AGE from friends where AGE >= 20
단점
데이터 생성, 삭제, 변경 시 추가적인 작업이 필요하다.
특히 삭제 시 해당 데이터를 기대로 두고 Dirty 처리하여 사용하지만 않는다. 그렇기에 나중엔 인덱스의 데이터가 방대해질 수 있다. 변경은 삭제 처리를 하고 생성한다.
- DB의 약 10%정도의 공간이 필요하다.
- 데이터가 골고루 분포되어있지 않다면, 인덱싱의 효율자체가 낮다. ex) 성별로 인덱스를 구현하면 인덱스를 쓰는 이유자체가 없어진다.
자료구조
인덱싱 자료구조로는 B Tree를 주로 이용한다. 비교수식을 사용할 수 없는 Hash보다 B Tree가 보편적으로 사용된다.
B Tree
B Tree는 자식 수에 대한 일반화를 진행한다. ex) Key = n, 자식 수 = n + 1 이어야 한다. 이를 통해 기존 데이터를 건드리지 않고, 가리키는 형식으로 인덱싱한다.
Type(mysql)
Primary 인덱스
PK에 대한 인덱스이다. 클러스터 인덱스 생성 시 데이터 페이지 전체가 다시 정렬된다. 검색소도가 빠르나, DML 작업이 느리다. 테이블 당 하나씩만 만들 수 있다. 데이터가 많다면, 클러스터 인덱스 생성은 시스템에 부하를 준다
Secondary 인덱스
후보키에만 부여할 수 있는 인덱스이다. 위와는 다르게 정렬하지 않고, 별도의 페이지에 인덱스를 구성한다. Primary Indexing과 다르게 여러 개의 인덱스를 생성할 수 있다. 보조인덱스는 주로 PK에 대한 단서를 얻는다.
혼합 사용
보조 인덱스를 통해 PK에 대한 단서를 얻고, 얻은 단서를 바탕으로 Primary 인덱스 페이지를 통해 데이터에 접근한다. 덕분에 특정 칼럼(PK가 아닌)에 대한 조회를 진행할 때, 빠르게 검색할 수 있다.
** 보조 인덱스는 페이지의 위치가 아닌 PK를 저장한다. 데이터 생성/ 추가/ 변경시 PK를 기준으로 다시 정렬된다. 만약, 보조 인덱스도 페이지의 위치를 저장하면 DML 마다 PK기준으로 바뀌어버린 페이지 번호에 맞게 또 인덱스를 변경해야 하는 공수가 든다.
ex) A로 시작하는 단어는 PAGE 1000이나 중간에 A로 시작하는 단어가 많아져 B로 시작하는 단어를 저장했던 1001 PAGE를 A로 시작하는 단어로 사용해야 한다. 이와 같이 DML시 추가적인 페이지 정렬이 필요하다.
결합 인덱스
두 개 이상의 칼럼을 합쳐서 인덱스를 만드는 방법이다. 필터링 및 조건으로 같이 사용되는 칼럼 집합이 있을 경우 사용한다. AND 조건일 경우 유리하다.
첫번째 칼럼으로 최대한 많이 나누려고 한다. 정렬 또한 첫번째 칼럼에 따라 진행된다.
RDMS
특징
PK로는 고객이 넘겨준 데이터로는 하지 않음, 왜냐면 정보의 신뢰성 문제때문에, 주민번호와 고객번호 모두 유니크하지만 고객번호를 더 선호한다.
참조 무결성을 가져야 한다. 즉, 다른 PK를 참조할 때는(FK), 해당 도메인에 맞는 범위만 가져야 한다.
PK 설계
레코드의 최대 수를 예측하여 알맞은 타입으로 진행한다. 그렇기에 주로 UUID나 GUID를 기반으로 자연키보단 임의의 키를 만들어낸다.
관계란?(RDMS)
RDMS에서 관계가 없다면, 단순 엑셀 정도의 역할밖에 못한다. 실제 데이터들은 연관성을 가지고 있다. 우리는 이것을 관계로 표현한다.
1:1, 1:M(부자지간), M:N 3가지가 있다. M:N은 관계형 데이터베이스에선 표현할 수 없다. 그렇기에 중간에 별도의 테이블을 두어, 1:M, M:1로 분할한다.
이상(Anomaly)
예) {학번, 과목번호, 전공, 수업번호, 학점}
삽입 이상
불필요한 데이터를 삽입해야만, 데이터를 추가할 수 있는 문제
ex) 기본 키를 학번 + 과목번호로 두면, 아무 과목도 듣지 않은 학생은 학생 테이블에 추가할 수 없음
갱신 이상
같은 의미를 가진 데이터가 여러 테이블에 위치할 때, 일부만 변경되어 데이터들이 불일치하는 문제
ex) 학생 테이블에는 학점의 평균이 들어있고, 성적 테이블이 별도로 있을 때, 특정 과목의 성적이 변경될 때 학생테이블의 학점 평균도 변경하지 않으면 데이터가 일치하지 않는다.
삭제 이상
필요한 데이터까지 삭제되는 문제, 수강을 철회하면 데이터를 삭제하는데 이때 학번과 전공에 대한 필요 데이터도 삭제된다.
정규화
정규화의 목표는 중복된 데이터를 제거하여 무결성을 지키는 것이다. 만약, 중복된 데이터가 여러 곳에 흩어져있다면, 업데이트 시 연관된 모든 테이블을 수정해야 한다. 그렇지 않을 경우 무결성이 지켜지지 않고 이상현상이 발생한다.
제1 정규화: 하나의 속성에는 하나의 값만(원자성)
제2 정규화: 어떤 복합 키가 후보키일 때, 둘 중 하나의 키로 다른 칼럼을 결정할 수 있으면 안된다.
제3 정규화 + BCNF: 후보키를 제외하고, 별도의 칼럼이 다른 칼럼을 결정할 수 있으면 안된다.
- BCNF(Boyce-Codd) 결정자가 후보키 집합에 속해야 한다. 즉 후보키가 아닌 속성이 후보키를 결정하면 안된다.
제4 정규화: 다치 종속(A → B 종속성을 가질 때, 1:M 관계라면 A↠B로 표현한다.) R(A,B,C)가 있을 때 A와 B사이에는 다치 종속이지만, B와 C는 아무 연관이 없을 때 이를 분리해야 한다.
제5 정규화: 조인 종속이 없어야 한다. A 관계를 B,C로 분해했다. B,C를 조인했을 때 A가 된다면 조인 종속성이 있는 것이다. / 자주 사용하진 않는다.
반정규화
정규화를 통해 분해가 많아지면, 데이터를 조회할 때 JOIN 연산이 많아진다. 이로 인해 응답시간이 느려질 수 있다. 성능저하가 심하면, 반정규화를 적용한다. 정규화에 반하는 작업으로, 하나의 테이블에 데이터를 통합한다.
대상
자주 사용하는 테이블, 항상 일정 범위만 조회하는 경우, 대량의 데이터, 성능 이슈, 조인이 너무 많아 조회가 어려운 경우
주의사항
데이터 무결성을 주의하며 반정규화를 진행해야 한다.
예를 들어 학생 테이블(ID, 이름, 전공), 학점 테이블(ID, 학생ID, 수업ID, 학점)이 있을 때 우리는 자주 어떤 학생에 대한 학점 목록을 검색한다고 한다. 그렇다면 SQL은 다음과 같다.
1
2
3
4
5
6
7
8
select ...
from 학생
join (
select 학점, 수업.수업이름, 학생ID
from 학점
join 수업 on 학점.수업ID = 수업.수업ID
) 병합 on 학생.학생ID = 병합.학생ID
where 학생ID = "김xx"
학생 테이블과 학점 테이블의 레코드가 너무 많다면, JOIN 연산이 부담된다. 이런 경우 학점테이블에 학생 이름과 수업 이름을 추가한다. 그러면 JOIN 연산없이 데이터를 가져올 수 있다. 무결성을 조금 포기하고, 성능을 높이는 것이다.
**주의, 그렇다고 학생ID를 제거하면 안된다. 참조 무결성을 보장해야 한다.
트랜잭션
데이터베이스를 변화시키는 작업 논리적 단위이다. 트랜잭션은 완전해야 한다. 하나의 트랜잭션에 A,B 연산이 있다면 A,B 연산 모두 완료가 보장되어야 한다. 트랜잭션 작업이 발생할 때 Lock(잠금)을 진행하여 독립성을 보장받는다. 만약 중간에 에러가 발생하면, 트랜잭션 모든 작업이 취소되거나, 다시 작업을 완료해야 한다. 4가지 원칙을 만족해야 한다.
특징
ACID로 Atomicity(원자성), Consistency(일관성), Isolation(독립성), Duration(지속성)입니다.
- 원자성은 하나의 완전한 연산이어야 한다 / 작업 중간에 오류가 발생하면 롤백가능해야 함(원래 상태)
- 일관성은 트랜잭션은 항상 일관되어야 한다. / 고정 요소(참조무결성 등)은 같고, 참조한 DB도 같아야함
- 독립성은 다른 연산의 영향을 받으면 안된다. > lock을 통해 진행, 주로 Row에 Lock을 진행
- 지속성은 트랜잭션의 실행이 완료되면 영원히 반영되어야 함을 의미합니다.
교착상태
주로 행마다 Lock을 진행하므로, 여러 트랜잭션이 동시에 진행되면 교착상태가 될 수 있다. 빈도를 낮추기 위해 트랜잭션을 자주 커밋하며, 읽기 잠금을 피하거나, 테이블 단위의 잠금을 획득한다.
트랜잭션의 격리 수준
- READ UNCOMMITTED(읽기에는 제한이 없음 / 정합성의 문제), 커밋되지 않은 데이터를 읽으니 만약 롤백된다면 “이상한 값”으로 데이터를 처리하는 문제가 있다.
- READ COMMITTED(커밋된 데이터만 읽기) 주로 사용 / 쓰기가 진행중이면 백업된 레코드를 가져옴)
- 트랜잭션 중 같은 레코드의 업데이트(커밋)이 완료되면 다시 select 했을 때 값이 달라지는 문제
- REPEATABLE READ:(반복가능한 읽기) 변경전의 레코드를 기록함, 가장 최근의 버전을 선택 / 변경 전 후의 데이터 모두 존재 / 가장 낮은 ID의 데이터를 확인함 / 행추가-삭제는 막지않
- SELIALIZABLE: 레코드에 동시접속이 불가능
문제
- Dirty Read: 커밋되지 않은 데이터를 읽어오므로, 만약 롤백이 일어나면 이상한 데이터를 가지고 처리하게 된다.
- 반복 불가능한 조회: 같은 트랜잭션안에서 여러 번 조회하면 값이 다른 문제
- 팬텀 리드: 조회해온 행 혹은 결과가 없어지는 현상이다.
Statement vs PreparedStatement
Statement는 입력받은 데이터를 그대로 실행하는 방법이다. PreparedStatement는 미리 쿼리를 작성하고, 매개변수로 채우는 방법이다.
- Statement
1
2
3
4
//Creating The Statement Object
Statement prep_statement = con.createStatement();
// Statement
prep_statement.executeUpdate("CREATE TABLE STUDENT(ID NUMBER NOT NULL, NAME VARCHAR)");
- PreparedStatement
1
2
3
4
5
PreparedStatement prep_statement = con.prepareStatement("update STUDENT set NAME = ? where ID = ?");
// 데이터를 받아 처리
prep_statement.setString(1, "RAM");
prep_statement.setInt(2, 512);
prep_statement.executeUpdate();
SQL Injection
해커에 의해 조작된 SQL 쿼리문이 데이터베이스에 그대로 전달되어 비정상적 명령을 실행시키는 공격 기법
ex) 패스워드의 값으로, 1234; DELETE USERS WHERE ID > 5;
를 입력하면 테이블의 데이터가 날라갈 수 있다.
방어 방법
- input 값을 받을 때, 유효한지 검사한다.
- 에러 로그를 클라이언트에게 보내지 않는다.
- preparestatement를 사용한다.
- 구문을 분석하는 과정은 초기에 진행하며 바인딩 데이터는 컴파일언어로 처리되기에 문법적인 의미를 가질 수 없다.
스키마란
데이터베이스에서 스키마란 데이터베이스에 저장된 자료의 구조 및 관계를 표현한 것이다. 각 Entity 및 Entity의 속성, 관계, 제약조건 등을 기술한 것이 그 내용이 됩니다. 스키마는 데이터 사전(데이터 항목들에 대한 정보를 지정한 중앙 저장소)에 저장되어 관리됩니다. 시간에 따라 불변적인 특성을 가집니다.
NoSQL
말그대로 관계형 DB의 반대다. 스키마도 없고, 관계도 없다! NoSQL에서는 레코드를 문서(documents)라고 부른다. 여기서 SQL과 핵심적인 차이가 있는데, SQL은 정해진 스키마를 따르지 않으면 데이터 추가가 불가능했다. 하지만 NoSQL에서는 다른 구조의 데이터를 같은 컬렉션에 추가가 가능하다.
NoSQL 모델의 종류는 아래와 같다.
모델 종류
Key-Value
1
2
3
{
"session_12345": "{userID: 'user101', status: 'active', lastLogin: '2024-05-06T12:00:00Z'}"
}
Document
1
2
3
4
5
6
7
8
9
10
11
12
{
"_id": "product123",
"name": "Smartphone",
"brand": "PhoneBrand",
"specifications": {
"color": "black",
"memory": "128GB",
"OS": "Android"
},
"price": 299.99,
"availability": true
}
Column Model
Column에 대한 스키마를 정의한다. 테이블 별 별도의 조인을 지원하진 않는다.
RowKey | OrderID | CustomerID | ProductID | Price | OrderDate |
---|---|---|---|---|---|
1 | 1001 | 501 | 2001 | 50 | 2024-05-06 |
2 | 1002 | 502 | 2002 | 150 | 2024-05-07 |
모델 선택
모델을 선택하는 방식은 CAP 이론을 따른다. CAP을 각각 살펴보며, 어떤 유형이 필요한지 확인한다.
일관성(Consistency)
NoSQL은 수평적으로 확장될 가능성이 높다. 이때, 데이터의 일관성이 보장되는 지의 여부이다. 만약 사용자가 데이터를 읽어올 때 노드 별로 아직 데이터 동기화가 마무리 되지 않았다면 사용자 마다 다른 데이터를 읽어온다. 이런 경우 일관성이 만족되지 않은 것이다.
가용성(Availability)
클라이언트 요청에 항상 응답 가능해야 한다. 이를 위해 RSM(복제)를 이용하기도 한다.
네트워크 분할 허용성(Partition tolerance)
각 노드가 네트워크가 차단되어도, 각 네트워크 영역에서는 정상적으로 동작해야 한다.
[이미지 출처: https://www.geeksforgeeks.org/the-cap-theorem-in-dbms/]
SQLvs NoSQL
SQL: 데이터 중복 줄일 수 있음 / 확장성이 안좋음 / 수직적 확장만 가능 / 스키마 결정 이후 추천
NoSQL: 확장성 좋음 / 중복된 데이터 다수 / 수평적 확장 가능 / 데이터 수정 시 중복된 데이터 때문에 비용 큼 / 초기 단계 추천
샤딩
하나의 데이터 베이스에 데이터가 급격하게 늘어나면, 자연스레 용량 이슈 + 성능 저하가 발생한다. 이를 위해 DB 트래픽을 분산할 목적으로 샤딩을 선택한다.
샤딩은 데이터를 구분할 Key 값이 있어야 한다. (샤딩키)
샤딩키를 바탕으로 여러 데이터베이스에 데이터를 분산해서 저장한다.
샤딩 종류
Modular
모듈러 연산(%)으로 진행한다. ex) 샤딩키를 기반으로 %5 를 통해 5개의 데이터베이스에 데이터를 저장한다.
Router
라우터 샤딩은 규칙기반이다. 네트워크의 라우터와 같이 특정 대역과 노드를 맵핑한다. ex) 0~100 : 노드1, 101 ~ 500: 노드2
Entity Group
SQL에 유리한 샤딩방법으로, 관계가 있는 엔티티끼리 같은 노드에 위치하는 방식으로 샤딩을 적용할 수 있다. 각 유저별 관계가 있는 데이터를 같은 노드에 둔다. 쿼리를 수행할 때, 단일 노드에서 수행할 가능성이 커진다.
SQL, NoSQL
SQL은 스토리지를 최대한 활용하기 위해 샤딩을 이용한다. 물론 NoSQL도 샤딩을 이용한다. Redis 에서 확인할 수 있다. ex) 노드1에 A 고객이 존재한다면, 유저 A와 관련있는 주문 목록, 상품 목록을 노드1에 저장한다. 상품 목록과 같은 경우, 여러 노드에 중복된 상품이 존재할 수 있다.
vs Partitioning
참고자료
https://medium.com/@kumar.barmanand/cap-theorem-and-nosql-databases-589e26e15905
https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6
https://www.geeksforgeeks.org/the-cap-theorem-in-dbms/
https://www.geeksforgeeks.org/difference-between-3nf-and-bcnf-in-dbms/
https://techblog.woowahan.com/2687/
https://gyoogle.dev/blog/computer-science/data-base/SQL Injection.html