면접 준비 - (3) 데이터베이스

Chaeyun·2024년 4월 21일
0

CS

목록 보기
4/6

데이터베이스

데이터베이스란?

데이터를 관리하기 쉽도록 어떠한 규칙에 맞춰 구조화한 데이터 모음.
데이터 베이스 시스템은 각각 자기만의 규칙에 따라 데이터베이스를 구조화하고 관리하하는 시스템.

데이터베이스 시스템을 사용하는 이유는?

데이터베이스 시스템을 사용하면 데이터를 구조화하여 관계를 파악하기 쉽고 관리하기 쉽습니다.
예를 들어 학교에서 어떤 반에 어떤 학생이 속했는지에 대한 정보를 엑셀로 관리한다고 했을 때, 데이터베이스 시스템이 없으면 어떤 학생의 이름이 바뀌거나 번호가 바뀌었을 때 관련한 정보가 적혀있는 모든 문서를 찾아 업데이트해야 합니다.
그리고 학생의 반, 번호, 이름을 모든 문서에 적어놔야 하기 때문에 데이터의 중복이 많습니다.
하지만 이 경우 관계형 데이터베이스 시스템을 사용하면 변경이 일어났을 때 단지 student 테이블에서 학생의 정보만 업데이트해주면 됩니다. 그리고 모든 문서에 학생의 반,번호, 이름을 다 적을 필요없이 학생 번호만 적어주면 됩니다.

데이터베이스 시스템은 뭐가 있죠?

RDBMS와 NoSQL
RDBMS는 열과 행으로 이루어진 2차원 테이블 형태로 데이터를 구조화함.
또한 테이블 간 관계 정보를 관리하여 정보 간의 관계를 파악하기 쉬움.
이것으로 인한 장점은 데이터 중복을 줄이고 일관성을 유지하기 쉽다는 것이 있음. 대신 이로 인해 샤딩, 파티셔닝 등 데이터를 분산하여 저장할 경우 복잡도가 증가하고 운영 비용이 크게 든다는 단점이 있습니다.

NoSQL은 RDBMS와 같은 형태로는 데이터를 관리하기 어렵거나 확장성 등의 이유로 생겨난 데이터베이스 시스템을 묶어서 부르는 말입니다.

  • Key-value 스토어: 단순 키-밸류 값 저장을 위해 RDBMS를 쓰는 경우 성능이 떨어지기 때문에 조회, 쓰기 성능을 높인 키 밸류 스토어. redis가 있따.
  • document 스토어: 스키마를 문서 단위로 유연하게 변경 가능하고, 수평 확장이 용이하다. MongoDB의 경우 config서버-mongos서버-shard서버를 통해 자체로 샤딩을 지원한다.
  • graph 데이터베이스: 데이터 간 관계가 트리 구조와 같이 복잡하고 깊어지는 경우 RDBMS로 데이터를 관리하기 어렵다. 이로 인해 생겨난 그래프 형태로 데이터를 저장하고 관리하는 데이터베이스. 예시로 Neo4j 가 있다.

왜 RDBMS는 데이터를 분산할 경우 관계 정보를 유지하고 관리하는 게 어렵죠?

이에 대해 얘기하려면 샤딩에 대해 먼저 알아야 함.
샤딩은 데이터를 수평적으로 나눠서 여러 서버에 분산 저장하는 것이다.
예를 들어 학생이 10명있으면 5명은 a서버에, 5명의 b서버에 정보를 저장해두는 것이다.

샤딩을 하게 되면 RDB의 특징이 되는 기능에 제약이 생겨 그 전처럼 RDB를 사용하지 못한다.
예를 들어 물리적으로 다른 노드의 데이터베이스와는 join 연산을 할 수 없고, 두 개 이상의 샤드에 하나의 트랜잭션이 접근할 수 없다는 등의 문제가 있다.

이로 인해 조인 연산이나 트랜잭션 처리와 같은 복잡한 쿼리의 경우 샤딩 이전 환경의 쿼리보다 느리고 에러 발생 빈도도 높아질 위험이 있다. 무엇보다 샤딩은 유지보수, 확장 관점에서 복잡도가 매우 높아진다. 따라서 샤딩을 도입하더라도 조인이 없는 단순한 경우에 적용할 것을 권한다.

그리고 수평적 파티셔닝인 샤딩은 이러한 부작용(운영적/프로그래밍적 복잡도 상승)이 많기 때문에 샤딩을 도입하기 전 다양한 방법을 고려할 수 있다.

  • Scale-in(더 좋은 컴퓨터로 바꾸는 것)
  • Read 부하가 크다면? Cache나 Database의 Replication 적용
  • Table의 일부 컬럼만 자주 사용한다면? Vertically Partition (Data를 Hot, Warm, Cold Data로 분리하는 것)

개인적인 궁금점: cold storage 적용시 정보가 hot storage에 있는지 cold storage에 있는지 어떻게 구분하고 찾아가는가? 이것을 config server-mongos server와 같이 구분하고 찾아갈 수 있도록 하는 서버를 자체적으로 구현하는 건지?

참고1: https://velog.io/@onejaejae/why-sharding-is-difficult-in-rdbms
참고2: https://nesoy.github.io/articles/2018-05/Database-Shard

샤딩과 파티셔닝의 차이점이 뭐죠?

샤딩은 데이터베이스를 여러 서버에 분산해서 저장하는 걸 의미하고, 파티셔닝은 동일한 서버내에서 큰 테이블을 여러 개의 테이블로 나눠서 저장하는 걸 뜻합니다.
샤딩은 서로 다른 서버로 데이터가 분산되기 때문에 분산 부하의 장점이 있습니다. 하지만 RDBMS의 경우 샤딩을 자체적으로 지원하지 않기 때문에 애플리케이션 레벨에서 구현을 해야하는데 이 경우 데이터베이스 기능을 원활히 사용하기 어렵다는 단점이 있습니다. 예를 들어 물리적으로 떨어진 데이터베이스 간에 join이 어렵고, 두 샤드 서버를 한 트랜잭션이 함께 커버할 수 없습니다.
MongoDB나 nStore같은 데이터베이스는 자체적으로 샤딩을 지원하고 있어 샤딩을 구축하기가 쉽고 스케일 아웃에 유리합니다.
샤딩은 hash sharding, range sharding 등이 있음. 해시의 경우 모듈러 연산으로 구현할 수 있는데 전체 샤딩 서버 갯수가 달라지면 함수도 달라지기 때문에 데이터 재정렬이 필요함. 따라서 갯수가 고정된 경우 사용 권장.
range는 PK 값을 범위에 따라 분리함. 갯수 증설시 큰 리소스가 소요되지 않음. 그래서 데이터가 급격히 증가하는 경우 range가 해시보다 나음.
물론 range도 나눠놓은 샤딩 서버중 특정 서버에만 트래픽이 몰리는 경우 샤딩 기준을 다시 고려해야할 수 있음.

파티셔닝은 mysql과 같은 RDBMS도 지원하고 있는데요. 장점은 테이블이 너무 커져서 인덱스가 물리 메모리를 초과하면서 쿼리 성능이 떨어지는 경우 이를 파티션닝하면 범위 select와 단일 insert 등의 성능이 크게 높아질 수 있다는 것입니다. 파티셔닝은 크게 세가지 방식으로 구현할 수 있는데 list, range, hash입니다. list는 정해둔 list 값에 해당하면 파티션에 속하는 방법이고, range는 정해둔 범위에 해당하면 파티션에 속하는 방법입니다. hash는 특정 컬럼의 값을 해싱하여 나온 결과값으로 파티션을 선택하는 방법입니다.

참고: https://hudi.blog/db-partitioning-and-sharding/

NoSQL이란?

RDBMS의 경우 데이터 조작을 위해 SQL이란 쿼리 언어를 사용하게 되는데, RDBMS와 형태, 구조 등이 많이 달라 같은 범주로 묶이지 않게 된 다양한 데이터베이스 시스템을 묶어 NoSQL이라고 부릅니다.

NoSQL은 이러한 relational database로 저장하기에 데이터의 구조가 알맞지 않거나 성능이 떨어져 생겨난 불편을 해결하기 위해 생겨났습니다.

만약 컴퓨터에 단지 key-value 값만 저장하고 싶다면 여러 테이블간 관계에 대한 정보가 필요하지 않습니다. 하지만 이러한 정보를 관리하는 RDB 특성상 단순 key-value만 저장하고 관리하는 dbms보다 속도가 느립니다.
따라서 key-value만 저장하면서 빠르게 읽고 쓸 수 있는 database가 필요한 경우 redis를 사용할 수 있습니다.

또 relational dbms의 경우 테이블 구조를 처음에 설계하여 그에 맞춰 스키마를 만들고 사용하게 됩니다. 이를 수정하려하는 경우 다른 레코드나 테이블에 영향이 갈 수 있기 때문에 구조를 바꾸는 것에 부담이 있습니다. 하지만 NoSQL 중 문서 형태로 데이터를 관리하는 MongoDB의 경우 문서 형태가 고정되어 있지 않고 자유롭게 변경이 가능합니다. 따라서 같은 스키마여도 필드값을 자유롭게 추가하고 변경할 수 있습니다.

최근 NoSQL로 넘어가는 또다른 이유는 성능과 확장성 때문입니다.
RDBMS의 경우 샤딩이나 레플리케이션을 통해 스케일 아웃을 할 수 있는데 이에 드는 구축 비용이나 운영 비용이 크기 때문에 대부분 스케일 업을 통해 확장을 합니다. NoSQL 중 하나인 MongoDB의 경우 자체적으로 제공해주는 HA과 분산 솔루션이 있기 때문에 스케일 아웃의 구축과 관리가 훨씬 간편합니다.

또다른 NoSQL인 그래프 데이터베이스의 경우 a가 b와 친구과 b는 c,d와 친구고, c는 f,e,a와 친구고 이런 식으로 데이터 간의 관계성이 트리 구조로 깊어지는 경우 이를 RDBMS로 표현하기가 어렵습니다. 왜냐하면 조인을 많이 할수록 RDBMS는 조회 성능이 떨어지기 때문입니다... 이런 경우 데이터 구조화를 위해 그래프 데이터 베이스를 사용할 수 있습니다.

NoSQL의 단점?

NoSQL은 역정규화를 기본으로 하고 있어 조인에 대한 성능이 많이 낮다.
따라서 관계성이 있는 정보를 각 데이터가 유지해야 하는데 이 경우 nested 데이터의 수정이 일어나면 모든 문서를 찾아 수정해줘야 하기 때문에 번거롭고 느리다.

RDBMS의 장점?

RDBMS의 장점은 데이터의 중복을 줄이고 데이터의 일관성을 유지하기가 쉽다는 것입니다.

학교에서 학생에 대한 정보를 문서로 관리하고 있다고 했을 때 학생이 언급되는 모든 문서에 학생의 이름, 반, 번호, 성별 등을 모두 써야한다.
하지만 RDBMS의 경우 문서마다 학생 번호만 가지고 있으면 된다.
그리고 학생의 전화번호가 변경되었다고 했을 때 전자의 경우 모든 문서를 찾아 전화번호를 일일히 수정해주어야 한다. 만약 실수로 어떤 문서는 업데이트하지 않는 경우 데이터의 일관성이 깨지게 된다. RDBMS의 경우 학생 테이블에서 전화번호를 업데이트해주면 다른 데이터까지 일일히 찾아 업데이트할 필요가 없다. 따라서 일관성을 유지하기 쉽다.

RDBMS

RDBMS는 어떻게 데이터의 무결성(일관성)을 보장하는가?

RDBMS는 키와 트랜잭션을 통해 데이터의 일관성을 유지한다.
기본키를 통해 각 레코드를 식별하고, 외래키를 통해 데이터 조작이 관계가 있는 테이블 간에도 일관되게 반영되고 관리되도록 한다.
트랜잭션은 업무적으로 의미 있는 논리적인 일의 처리 단위를 보장하기 위한 것입니다. 예를 들어 돈을 이체하는 일을 처리할 때 한 사용자의 계좌에서 돈을 빼는 것만 완료되고 다른 계좌에 돈을 추가하는 작업이 완료되지 않으면 사용자가 원하는 일관된 상태가 깨집니다.
따라서 일부 작업이 실패하면 모든 작업을 롤백하고 모든 작업이 성공했을 때만 데이터베이스에 반영하도록 하여 비즈니스적으로도 의미있게 데이터의 일관성을 보장하고 있습니다.

키란?

레코드를 식별하는 속성 또는 속성들의 집합

유일성과 최소성을 만족해야 함.
유일성은 레코드를 유일하게 식별할 수 있어야 한다는 것이고, 최소성은 레코드를 식별할 수 있는 최소 단위여야 한다는 것이다.

키의 종류에는 슈퍼키, candidate key, 대체 키, primary key, foreign key 등이 있다.
슈퍼 키는 레코드를 유일하게 식별할 수 있는 속성들의 집합이고,
후보 키는 레코드를 유일하게 식별할 수 있는 최소한의 속성들의 집합임.
만약 학생 테이블에 학생 번호, 이름, 나이 등이 있다면 학생 번호, 이름, 나이의 집합은 슈퍼키는 될 수 있으나 후보 키는 되지 못함. 왜냐하면 학생 번호가 유일하게 식별할 수 있는 최소한의 속성이기 때문. 즉, 학생 번호는 슈퍼키이자 후보 키가 될 수 있다.
기본 키는 후보키 중 선택된 키이다. 당연히 각 레코드를 식별할 수 있어야 하므로 null이거나 중복된 값이 있으면 안된다.
대체 키는 후보키중 선택되지 못한 키이다. 예를 들어 학생 테이블에 주민 번호도 있고, 기본 키로 학생 번호가 선택되었을 때 주민 번호는 대체 키가 된다.
외래 키는 다른 테이블의 기본키를 참조하는 속성을 말한다.

키를 통해 레코드를 식별하거나 인덱스를 설정하거나 무결성을 유지하는 등의 일을 할 수 있다.

무결성이란?

데이터베이스에서 데이터가 정확하고 일관되게 유지하는 것을 말한다.

정확하다는 것은 중복이나 누락이 없다는 것을 말하고,
일관하다는 것은 데이터에 정해놓은 규칙을 데이터를 수정한 이후에도 계속해서 유지하는 것을 말한다.

무결성에는 개체 무결성, 참조 무결성, 속성 무결성 등이 있다.
개체 무결성은 기본키가 null이거나 중복값을 가지면 안된다는 것을 말한다.
참조 무결성이란 외래키는 null이거나 참조하는 다른 테이블의 기본키와 같은 값을 가져야 한다는 것이다.
속성 무결성은 속성 값과 관련해 걸어둔 제약이다. 데이터 타입, nulluable, 기본 값 등의 제약이 있을 수 있다.

참고: https://dataonair.or.kr/db-tech-reference/d-lounge/technical-data/?mod=document&uid=236559

트랜잭션

RDBMS는 트랜잭션을 어떻게 구현하고 있는가?

락과 언두로그로 트랜잭션을 구현하고 있다.
만약 한 트랜잭션이 시작되면 접근하는 레코드에 락을 건다. 그리고 해당 트랜잭션이 커밋되거나 롤백될때까지 다른 트랜잭션이 접근하지 못하게 할 수 있다.
하지만 이런 경우 여러 트랜잭션이 하나의 레코드에 동시에 접근할 수 없어 동시성이 매우 떨어질 수 있다.
따라서 mysql의 경우 언두로그를 통해 트랜잭션의 Repeatable Read 격리 수준을 구현하고 있다.
언두로그란 한 트랜잭션이 레코드를 수정하는 경우 레코드별로 이전 상태를 저장해둔 것이다.
트랜잭션이 데이터를 조작하는 것은 바로 InnoDB 버퍼 풀에 반영되고 조작 전의 결과를 언두로그로 생성해 보관하다가 롤백이 일어나면 이 언두로그에 있는 내용을 버퍼 풀로 덮어씌운다.

만약 커밋 전 다른 트랜잭션이 조작된 레코드를 읽으려는 경우 버퍼 풀이 아니라 언두 로그에서 값을 읽어온다. 이때 transaction_id를 기준으로 언두 로그를 기준으로 값을 읽어온다. 따라서 중간에 또다시 값이 바뀌더라도 트랜잭션은 계속해서 동일한 값을 읽어올 수 있다.

InnoDB 버퍼 풀이란?

테이블이나 인덱스 데이터를 캐시 하는 메모리 영역. 데이터를 메모리에서 직접 액세스 하여 I/O 작업 시 속도가 빠름.
변경 사항을 버퍼 풀에 가지고 있다가 나중에 한꺼번에 디스크로 덮어씌울 수 있음. 이때 장애로 인해 변경 사항을 디스크에 쓰지 못하는 상황을 위해 리두 로그를 관리함.

버퍼 풀은 페이지를 관리하기 위해 세개의 리스트를 가지고 있다. free list, LRU list, flush list인데 free list는 비어있는 페이지고 LRU list는 최근에 사용된 페이지고 flush는 변경된 페이지를 담아놓는 목록이다.

변경 사항이 생기면 InnoDB는 변경 사항을 버퍼 풀에 저장하고 리두로그를 디스크에 쓰고, flush된 페이지를 flush list에 추가한다.

Redo 로그란?

commit이 일어났을 때 바로 변경사항을 disk에 반영하는 게 아니라 버퍼 풀에 저장해놓는다. 이를 통해 disk I/O를 줄이는데 만약 버퍼 풀이 디스크에 반영되지 않은 상태에서 에러가 발생하면 데이터가 날라가게 된다. 이를 방지하기 위해 디스크에 변경 사항을 저장해둔 것이다.

리두 로그는 고정 크기 파일을 연결해서 순환 고리처럼 사용한다. 따라서 동기화 되기전 데이터 변경이 계속 발생하면 파일이 덮어씌워질 수 있다.

따라서 리두 로그 크기를 적절히 선정해야 한다. 크기가 너무 작으면 자주 동기화를 해야 하기 때문에 쓰기 지연 효과를 보기 힘들고, 너무 크면 중간에 갑자기 동기화를 해야하는 경우 많은 페이지를 반영해야 하기 때문에 시간이 오래걸릴 수 있다.
버퍼풀 100GB 이하는 5~10GB 수준으로 리두 로그 공간을 선택하고 조금씩 늘려가며 최적값을 찾는 게 좋다고 함.

참고: https://yelimkim98.tistory.com/55

트랜잭션 격리 수준이란?

트랜잭션 간에 얼마나 독립적으로 작동하도록 할 것인지를 결정하는 수준이다.
mysql은 repeatable read를 채택하는데 이는 트랜잭션이 시작한 후 처음 읽어온 값에 대해 후에 다시 접근하는 경우 처음 읽었던 값을 그대로 읽도록 하는 것이다. 만약 그 사이에 해당 값이 변경되었어도 처음 읽었던 값을 읽어온다.
그리고 read committed가 있는데 이건 commit된 값을 읽어오도록 하는 것이다. 만약 트랜잭션이 시작되고 처음 읽었던 값이 1이었는데 그 후에 다른 트랜잭션이 해당 값을 2로 수정해서 커밋했으면 해당 값을 다시 읽을 때는 2로 읽어진다.
read uncommitted는 트랜잭션간 독립성이 매우 낮은 것인데 만약 1로 읽었던 값을 다른 트랜잭션이 수정해서 아직 commit하기 전인 상황에서 다시 접근하면 2로 읽게 된다. commit되지 않았어도 해당 값을 읽어온다는 점에서 transaction 간에 영향이 클 수 있다.
마지막은 serializable인데 이건 말그대로 순차적으로만 트랜잭션이 접근할 수 있다는 것이다. 만약 트랜잭션이 어떤 값을 1로 읽어온다면 해당 트랜잭션이 커밋이나 롤백을 통해 종료될 때까지 다른 트랜잭션은 해당 레코드에 접근할 수 없다.

트랜잭션 격리 수준에 따라 여러 문제가 발생할 수 있는데
먼저 serializable은 문제가 발생하진 않지만 성능이 매우 떨어질 수 있어 동시성이 중요하지 않는 경우에만 사용한다.
read uncommitted는 아직 커밋되지 않은 값을 읽어올 수 있는 dirty read 문제가 있을 수 있다.
read committed는 non-repeatable read문제라고 해서 같은 트랜잭션인데 처음 읽었던 값과 나중에 다시 같은 값을 읽어올 때 값이 달라지는 문제가 있을 수 있다.
repeatable read는 phantom read라고 해서 범위 조회를 해서 처음에 행이 있었는데 다시 범위 조회를 하면 행이 없어지는 문제가 있을 수있다. 반대로 없던 행이 생기는 경우도 phantom read라고 한다.
read uncommited는 모든 세 문제가 나타날 수 있고, read committed는 non-repeatable read와 팬텀 리드가 나타날 수 있다.

락의 종류? 언제 어떤 락?

락의 정도에 따라 s-lock, x-lock이 있다. s-lock은 다른 트랜잭션이 읽기를 허용하고 쓰기는 금지하는 락이고 X-lock은 읽기도 금지하는 락이다.
s-lock이 걸린 레코드에 다른 트랜잭션이 s-lock을 거는 것은 허용되나 s-lock에 x-lock을 걸거나, x-lock에 s-lock이나 x-lock을 거는 것은 금지된다.
s-lock은 selcet ... for share 쿼리를 통해 걸 수 있으며, serializable 격리 수준에서는 그냥 select가 select ... for share로 처리된다.
x-lock은 update, delete 쿼리를 실행하거나 select ... for update 쿼리를 통해 걸 수 있다.

락의 범위에 따라 레코드락, 갭락, 넥스트 키 락이 있다.
레코드락은 DB 인덱스의 레코드에 락이 걸리는 것이다.
갭락은 레코드와 레코드 사이 간격에 락이 걸리는 것이다. 예를 들어 pk가 3인 레코드와 6인 레코드가 있을 때 이 두 레코드 사이 간격이 갭락이 걸리면 pk가 4,5인 레코드에 대해서는 삽입,수정,삭제가 금지된다.
갭락은 read committed에서는 쓰이지 않음.
넥스트 키 락은 레코드락과 갭락이 복합적으로 쓰이는 경우로 만약 범위 조회에 대해 락을 걸면 넥스트 키 락이 쓰인다. 예를 들어 pk가 3이 넘는 값에 대해 락을 걸면 3이 넘는 레코드들에 대해 레코드간 갭락과 레코드락이 걸린다. (갭락: 3-4, 4-5, ... , 레코드락: 4,5,...)
repeatable read에서 팬텀 리드를 방지하기 위해 넥스트 키 락을 사용함.

아 글로벌 락이나 테이블락인 인텐션 락도 있음

참고: https://jaeseongdev.github.io/development/2021/06/16/Lock%EC%9D%98-%EC%A2%85%EB%A5%98-(Shared-Lock,-Exclusive-Lock,-Record-Lock,-Gap-Lock,-Next-key-Lock)/
참고: https://infoqoch.github.io/db/2022/05/17/db-lock.html

실제 락 테스트 블로그 : https://medium.com/@im_zero/innodb-%EB%9D%BD-%ED%85%8C%EC%8A%A4%ED%8A%B8-ab712d19b74f

정규화

정규화란?

이상현상을 없애고 무결성을 지키기 위한 데이터베이스 설계 방법

이상현상이란 불필요한 데이터 중복으로 연산, 조작시 발생하는 문제현상으로, 삽입 이상 갱신 이상 삭제 이상이 있다.
삽입 이상은 데이터 삽입 시 불필요한 것도 삽입해야하는것, 갱신 이상은 중복 데이터 중 일부만 변경하여 불일치가 생기는 것, 삭제 이상은 삭제를 원하지 않는 데이터도 함께 삭제되는 것

1,2,3,3bcnf
1 - 원자성
2 - 완전함수 종속(기본키의 부분집합으로 특정 컬럼을 식별 X)
ex) 학생 번호, 학생 정보, 수강 과목 코드, 과목 정보를 가지고 있을 때 학생 번호와 수강 과목 코드 집합이 기본키. (한 학생이 여러 과목을 수강할 수 있으므로)

이때, 수강 과목 코드로 과목 정보를 식별할 수 있음. 학생 테이블과 수강 테이블, 과목 테이블로 분리해야 함.

3 - 이행적 종속 제거(이행적 종속은 X->Y, Y->Z 인 것)
ex) 학생 테이블에 우편 번호, 도시, 구, 동호수 등 정보가 있을 때, 학생 번호 -> 우편 번호, 우편번호 -> 도시,구,동호수 로 식별이 가능하다.

이런 경우 주소 테이블을 분리해야 함.

bcnf - 모든 결정자가 후보키
후보키는 레코들의 식별할 수 있는 최소한의 속성 집합.
즉, 레코드를 식별할 수 없으면서 특정 컬럼을 식별할 수 있는 결정자가 존재하면 안됨.
학생 이름, 과목, 교수에서 학생 이름과 과목 집합으로 교수를 결정할 수 있으면서 교수로 과목을 결정할 수 있다. 같은 과목에 여러 교수가 있을 수 있으면서 교수는 하나의 과목만 담당할 때 교수로 과목을 결정할 수 있다. 하지만 교수로 학생을 포함한 레코드를 식별할 수는 없기 때문에 bcnf를 만족하지 않는다.

이 경우 학생, 과목 테이블과 교수, 과목 테이블로 분리해야 한다.

4는 다치 종속 제거, 5는 조인 종속 제거가 있음. 하지만 보통 bcnf까지만 하고 4부터는 안함.

정규화의 장단점

데이터 중복을 줄여 저장 공간을 절약하고, 이상 현상을 방지할 수 있음

너무 테이블을 많이 쪼개면 join 연산이 많이 발생할 수 있음. join이 많으면 응답 시간이 저하될 수 있으므로 read 연산 퍼포먼스에 따라 비정규화를 하는 것도 고려해볼 수 있음.

CRUD가 많이 일어나는 경우 정규화를 하는 것이 좋고, 조회 및 분석용으로 많이 사용하는 데이터베이스의 경우 반정규화를 고려 가능

그리고 복잡한 many-to-many 관계에서도 정규화를 하는 것이 좋다고 함.

join이 많으면 시간이 많이 걸리는 이유?

조인 과정이

인덱스

인덱스가 뭐고 인덱스가 필요한 이유?

인덱스는 데이터를 빨리 찾기 위해 메모리에 만들어놓은 색인용 자료구조이다.
disk I/O는 느리기 때문에 전체 데이터를 disk에서 탐색하는 건 성능이 많이 떨어진다. 따라서, 자주 사용되는 검색 조건 컬럼을 인덱스로 만들어두면 메모리에서 데이터 위치를 찾아서 바로 접근할 수 있다.

데이터를 수정삽입삭제하는 경우 인덱스도 수정하고 재정렬하는 과정이 필요하기 때문에 성능이 떨어질 수 있다.
조회가 많은 경우 인덱스를 잘 쓰면 되려 성능이 좋아질 수 있음.

참고: https://brunch.co.kr/@skeks463/25

인덱스가 어떻게 구현되어있는가?

mysql은 B-Tree로 인덱스가 구성되어 있음.
트리 구조의 최상위에 하나의 루트 노드가 존재하고 그 하위에 자식 노드가 붙어있는 형태. 인덱스의 리프 노드는 실제 데이터 레코드를 찾아가기 위한 주솟값을 가지고 있다.

InnoDB에서 레코드는 클러스터되어 저장되므로 기본적으로 프라이머리 키 순서로 정렬되어 저장된다. MyISAM 테이블은 세컨더리 인덱스가 물리적인 주소를 가지고, InnoDB 테이블은 논리적인 주소인 프라이머리 키를 가지고있다. 때문에 프라이머리 키를 저장하고 있는 B-Tree를 다시 검색해야 한다.

탐색 시간은 O(log n)임. 인덱스를 사용하지 않으면 O(n).

실제로 인덱스에 접근해 데이터를 찾아오는 과정?

80을 찾는 경우 먼저 루트 노드에 접근해서 다음 노드 위치를 찾음. 루트 노드에 50과 100이 있으면 그 사이 값이므로 50에 할당된 포인터를 통해 다음 노드로 가고, 이런 식으로 리프 노드까지 감. 리프 노드로 가면 실제 데이터 주소 또는 프라이머리 키 값이 있다.
참고: https://jeong-pro.tistory.com/242

인덱스 종류

프라이머리 인덱스, 세컨더리 인덱스
프라이머리 인덱스는 기본 키로 만들어진 인덱스고 세컨더리 인덱스는 기본키가 아닌 값으로 만들어진 인덱스이다.
프라이머리 인덱스는 리프 노드에서 실제 레코드 주소를 가지고 있고, 세컨더리 인덱스는 리프 노드에서 프라이머리 키 값을 가지고 있어 다시 프라이머리 인덱스를 통해 데이터에 접근해야함.
참고로, 세컨더리 인덱스는 커버링 인덱스가 아니면 무조건 디스크 랜덤 I/O가 발생함. 따라서 조회 후 접근하는 데이터 수가 많지 않아야 랜덤 I/O 횟수가 줄어 성능이 떨어지지 않음. 또는 커버링 인덱스를 구성하는 게 좋음.

dense index, sparse index도 있음. dense index는 모든 레코드에 대해 인덱스를 가지고 sparse index는 일부 레코드에 대해 인덱스를 가지는 것.

클러스터링에 대한 내용 참고 : https://yainii.tistory.com/38#%EC%--%A-%ED%--%-D%EB%-F%---Selectivity-%--%EA%B-%B-%EC%--%--%EC%--%B--Cardinality-

인덱스 스캔 방식

레인지 스캔, 풀 스캔, 루스 스캔, 스킵 스캔

레인지 스캔이 일반적인 인덱스 스캔. 인덱스에 대해 범위 탐색하는 것.
풀 스캔은 인덱스 전부 탐색하는 것. 예를 들어 (dept_no, emp_no) 같은 인덱스가 있을 때 emp_no에 대해 조건절이 있으면 결국 전부 탐색해야 함.
루스 스캔은 띄엄띄엄 인덱스를 스캔하는데 Group By, Min, Max 등의 최적화에서 사용함. select min(emp_no) where dept_no between 1 and 3 group by dept_no와 같은 경우 dept_no로 범위 스캔 후 제일 첫번째 값만 읽게 된다. emp_no도 순서대로 정렬되어있기 때문.
스킵 스캔은 몇가지 조건을 갖추면 발생하는 스캔인데, 먼저 커버링 인덱스여야 하며 where 절에서 앞순서 인덱스 정보가 없는 경우 앞 순서 정보를 추가해서 부분적으로 인덱스 스캔을 하는 것이다. 예를 들어, (성별, emp_no)과 같은 인덱스가 있을 때 where 절에 emp_no between 1 and 10이라는 조건만 있으면 자체적으로 성별 조건을 추가해 where 성별 = '여' and emp_no between 1 and 10 or 성별 = '남' and emp_no between 1 and 10 과 같이 처리하는 것이다. 이 경우 앞 순서 인덱스의 cardinality가 낮아야 성능이 떨어지지 않는다.

참고: https://jeong-pro.tistory.com/242
참고: https://yainii.tistory.com/38#%EC%--%A-%ED%--%-D%EB%-F%---Selectivity-%--%EA%B-%B-%EC%--%--%EC%--%B--Cardinality-
참고: https://devroach.tistory.com/40#recentComments

인덱스를 언제 쓰는 게 좋은지?

인덱스는 데이터의 삽입, 수정, 삭제 조작에 대해서는 성능이 되려 떨어질 수 있다. 데이터 삽입 시에는 인덱스도 추가 후 정렬이 필요하다. 그리고 데이터 삭제 시에 인덱스 노드도 삭제되는 것이 아니라 미사용 표기만 하고 데이터가 남게 된다. 따라서 삭제가 많이 일어나면 인덱스의 크기가 비대해져 추후 조회 성능이 떨어질 수 있다. 수정시에는 삭제 후 삽입 개념이기 때문에 insert와 delete의 단점을 모두 가지고 있다.
그리고 cardinality가 적은 컬럼에 대해 인덱스를 생성하면 인덱스로 찾아낸 데이터의 범위가 넓어 다시 디스크 I/O가 많이 일어나게 되어 효율적이지 못하다.
따라서 수정보다는 조회가 많은 경우, 그리고 cardinality가 큰 컬럼에 대해 생성하는 것이 좋다.

참고: https://jeong-pro.tistory.com/242

0개의 댓글