Database : 데이터베이스

Database

CS 공부를 하면서 정리해둔 것입니다. 핵심 키워드는 Bold 해놓겠습니다!

데이터베이스

데이터베이스를 사용하는 이유

데이터베이스가 존재하기 이전에는 파일 시스템을 이용하여 데이터를 관리하였습니다. (현재도 부분적으로 사용되고 있습니다.) 데이터를 각각의 파일 단위로 저장하며 이러한 일들을 처리하기 위한 독립적인 애플리케이션과 상호 연동이 되어야 합니다. 이 때의 문제점은 데이터 종속성 문제와 중복성, 데이터 무결성입니다.

데이터베이스의 특징

  1. 데이터의 독립성

    • 물리적 독립성 : 데이터베이스 사이즈를 늘리거나 성능 향상을 위해 데이터 파일을 늘리거나 새롭게 추가하더라도 관련된 응용 프로그램을 수정할 필요가 없습니다.
    • 논리적 독립성 : 데이터베이스는 논리적인 구조로 다양항 응용 프로그램의 논리적 요구를 만족시켜줄 수 있습니다.
  2. 데이터의 무결성

    여러 경로를 통해 잘못된 데이터가 발생하는 경우의 수를 방지하는 기능으로 데이터의 유효성 검사를 통해 데이터의 무결성을 구현하게 됩니다.

  3. 데이터의 보안성

    인가된 사용자들만 데이터베이스나 데이터베이스 내의 자원에 접근할 수 있도록 계정 관리 또는 접근 권한을 설정함으로써 모든 데이터에 보안을 구현할 수 있습니다.

  4. 데이터의 일관성

    연관된 정보를 논리적인 구조로 관리함으로써 어떤 하나의 데이터만 변경했을 경우 발생할 수 있는 데이터의 불일치성을 배제할 수 있습니다. 또한 작업 중 일부 데이터만 변경되어 나머지 데이터와 일치하지 않는 경우의 수를 배제할 수 있습니다.

  5. 데이터 중복 최소화

    데이터베이스는 데이터를 통합해서 관리함으로써 파일 시스템의 단점 중 하나인 자료의 중복과 데이터의 중복성 문제를 해결할 수 있습니다.

데이터베이스의 성능?

데이터베이스성능 이슈는 디스크 I/O 를 어떻게 줄이느냐에서 시작됩니다. 디스크 I/O 란 디스크 드라이브의 플래터(원판)을 돌려서 읽어야 할 데이터가 저장된 위치로 디스크 헤더를 이동시킨 다음 데이터를 읽는 것을 의미합니다. 이 때 데이터를 읽는데 걸리는 시간은 디스크 헤더를 움직여서 읽고 쓸 위치로 옮기는 단계에서 결정됩니다. 즉 디스크의 성능은 디스크 헤더의 위치 이동 없이 얼마나 많은 데이터를 한 번에 기록하느냐에 따라 결정된다고 볼 수 있습니다.

그렇기 때문에 순차 I/O랜덤 I/O 보다 빠를 수 밖에 없습니다. 하지만 현실에서는 대부분의 I/O 작업이 랜덤 I/O 입니다. 랜덤 I/O순차 I/O 로 바꿔서 실행할 수는 없을까? 라는 생각에서부터 시작되는 데이터베이스 쿼리 튜닝랜덤 I/O 자체를 줄여주는 것이 목적이라고 할 수 있습니다.

Index

인덱스(Index)란?

인덱스는 말 그대로 책의 맨 처음 또는 맨 마지막에 있는 색인이라고 할 수 있습니다. 이 비유를 그대로 가져와서 인덱스를 살펴본다면 데이터는 책의 내용이고 데이터가 저장된 레코드의 주소는 인덱스 목록에 있는 페이지 번호가 될 것입니다. DBMS 도 데이터베이스 테이블의 모든 데이터를 검색해서 원하는 결과를 가져 오려면 시간이 오래 걸립니다. 그래서 칼럼의 값과 해당 레코드가 저장된 주소를 키와 값의 쌍으로 인덱스를 만들어 두는 것입니다.

DBMS 의 인덱스는 항상 정렬된 상태를 유지하기 때문에 원하는 값을 탐색하는데는 빠르지만 새로운 값을 추가하거나 삭제, 수정하는 경우에는 쿼리문 실행 속도가 느려집니다. 결론적으로 DBMS 에서 인덱스는 데이터의 저장 성능을 희생하고 그 대신 데이터의 읽기 속도를 높이는 기능입니다. SELECT 쿼리 문장의 WHERE 조건절에 사용되는 칼럼이라고 전부 인덱스로 생성하면 데이터 저장 성능이 떨어지고 인덱스의 크기가 비대해져서 오히려 역효과만 불러올 수 있습니다.

Index 자료구조

그렇다면 DBMS 는 인덱스를 어떻게 관리하고 있을까요?

B+-Tree 인덱스 알고리즘

일반적으로 사용되는 인덱스 알고리즘은 B+-Tree 알고리즘입니다. B+-Tree 인덱스는 칼럼의 값을 변형하지 않고(사실 값의 앞부분만 잘라서 관리합니다.) 원래의 값을 이용해 인덱싱하는 알고리즘입니다.

B+-Tree 인덱스 알고리즘에 대한 자세한 설명

Hash 인덱스 알고리즘

칼럼의 값으로 해시 값을 계산해서 인덱싱하는 알고리즘으로 매우 빠른 검색을 지원합니다. 하지만 값을 변형해서 인덱싱하므로, 특정 문자로 시작하는 값으로 검색을 하는 전방 일치와 같이 값의 일부만으로 검색하고자 할 때는 해시 인덱스를 사용할 수 없습니다. 주로 메모리 기반의 데이터베이스에서 많이 사용합니다.

왜 index 를 생성하는데 b-tree 를 사용할까요?

데이터에 접근하는 시간복잡도가 O(1)hash table 이 더 효율적일 것 같은데? SELECT 질의의 조건에는 부등호(<>) 연산도 포함이 됩니다. hash table 을 사용하게 된다면 등호(=) 연산이 아닌 부등호 연산의 경우에 문제가 발생합니다. 동등 연산(=)에 특화된 hashtable은 데이터베이스의 자료구조로 적합하지 않습니다.

Primary Index vs Secondary Index

클러스터(Cluster)여러 개를 하나로 묶는다는 의미로 주로 사용되는데, 클러스터드 인덱스도 크게 다르지 않습니다. 인덱스에서 클러스터드는 비슷한 것들을 묶어서 저장하는 형태로 구현되는데, 이는 주로 비슷한 값들을 동시에 조회하는 경우가 많다는 점에서 착안된 것입니다. 여기서 비슷한 값들은 물리적으로 인접한 장소에 저장되어 있는 데이터들을 말합니다.

클러스터드 인덱스는 테이블의 프라이머리 키에 대해서만 적용되는 내용입니다. 즉 프라이머리 키 값이 비슷한 레코드끼리 묶어서 저장하는 것을 클러스터드 인덱스라고 표현합니다. 클러스터드 인덱스에서는 프라이머리 키 값에 의해 레코드의 저장 위치가 결정(프라이머리 키로 정렬되기 때문에)되며 프라이머리 키 값이 변경되면 그 레코드의 물리적인 저장 위치(테이블에서 레코드의 위치를 말합니다.) 또한 변경되어야 합니다. 그렇기 때문에 프라이머리 키를 신중하게 결정하고 클러스터드 인덱스를 사용해야 합니다.

클러스터드 인덱스는 테이블 당 한 개만 생성할 수 있습니다. 프라이머리 키에 대해서만 적용되기 때문입니다, 이에 반해 non 클러스터드 인덱스는 테이블 당 여러 개를 생성할 수 있습니다.

결합인덱스(Composite Index)

인덱스로 설정하는 필드의 속성이 중요합니다. title, author 이 순서로 인덱스를 설정한다면 title 을 search 하는 경우, index 를 생성한 효과를 볼 수 있지만, author 만으로 search 하는 경우, index 를 생성한 것이 소용이 없어집니다. 따라서 SELECT 질의를 어떻게 할 것인가가 인덱스를 어떻게 생성할 것인가에 대해 많은 영향을 끼치게 됩니다.

Index 의 성능과 고려해야할 사항

SELECT 쿼리의 성능을 월등히 향상시키는 INDEX는 항상 좋은 것일까요? 쿼리문의 성능을 향상시킨다는데, 모든 컬럼에 INDEX 를 생성해두면 빨라지지 않을까요? 결론부터 말하자면 그렇지 않습니다. 우선, 첫번째 이유는 INDEX 를 생성하게 되면 INSERT, DELETE, UPDATE 쿼리문을 실행할 때 별도의 과정이 추가적으로 발생합니다. INSERT 의 경우 INDEX 에 대한 데이터도 추가해야 하므로 그만큼 성능에 손실이 따릅니다. DELETE 의 경우 INDEX 에 존재하는 값은 삭제하지 않고 사용 안한다는 표시로 남게 됩니다. 즉 row 의 수는 그대로인 것입니다. 이 작업이 반복되면 어떻게 될까요?

실제 데이터는 10 만건인데 데이터가 100 만건 있는 결과를 낳을 수도 있는 것입니다. 이렇게 되면 인덱스는 더 이상 제 역할을 못하게 되는 것입니다. UPDATE 의 경우는 INSERT 의 경우, DELETE 의 경우의 문제점을 동시에 수반합니다. 이전 데이터가 삭제되고 그 자리에 새 데이터가 들어오는 개념이기 때문입니다. 즉 변경 전 데이터는 삭제되지 않고 INSERT로 인한 split 도 발생하게 됩니다.

하지만 더 중요한 것은 컬럼을 이루고 있는 데이터의 형식에 따라서 인덱스의 성능이 악영향을 미칠 수 있다는 것입니다. 즉, 데이터의 형식에 따라 인덱스를 만들면 효율적이고, 만들면 비효율적인 데이터의 형식이 존재한다는 것입니다. 어떤 경우에 그럴까요?

이름, 나이, 성별 세 가지의 필드를 갖고 있는 테이블을 생각해봅니다. 이름은 온갖 경우의 수가 존재할 것이며 나이는 INT 타입을 갖을 것이고, 성별은 남, 녀 두 가지 경우에 대해서만 데이터가 존재할 것임을 쉽게 예측할 수 있습니다. 이 경우 어떤 컬럼에 대해서 인덱스를 생성하는 것이 효율적일까요? 결론부터 말하자면 이름에 대해서만 인덱스를 생성하는게 효율적입니다.

왜 성별이나 나이는 인덱스를 생성하면 비효율적일까요? 10000 레코드에 해당하는 테이블에 대해서 2000 단위로 성별에 인덱스를 생성했다고 가정해보면, 값의 range 가 적은 성별은 인덱스를 읽고 다시 한 번 디스크 I/O 가 발생하기 때문에 그 만큼 비효율적인 것입니다. (특정한 레코드를 찾을 때 범위가 작기 때문에 인덱스를 여러번 들러야 해서 그런 것 같습니다.)

정규화(Normalization)

1. 정규화는 어떤 배경에서 생겨났을까요?

한 릴레이션에 여러 엔티티의 애트리뷰트들을 혼합하게 되면 정보가 중복 저장되며, 저장 공간을 낭비하게 됩니다. 또 중복된 정보로 인해 갱신 이상이 발생하게 됩니다. 동일한 정보를 한 릴레이션에는 변경하고, 나머지 릴레이션에서는 변경하지 않은 경우 어느 것이 정확한지 알 수 없게 되는 것입니다. 이러한 문제를 해결하기 위해 정규화 과정을 거치는 것입니다.

1-1. 갱신 이상에는 어떠한 것들이 있을까요?

  • 삽입 이상(insertion anomalies) : 원하지 않는 자료가 삽입된다든지, 삽입하는데 자료가 부족해 삽입이 되지 않아 발생하는 문제점을 말합니다.
  • 삭제 이상(deletion anomalies) : 하나의 자료만 삭제하고 싶지만, 그 자료가 포함된 튜플 전체가 삭제됨으로 원하지 않는 정보 손실이 발생하는 문제점을 말합니다.
  • 수정(갱신)이상(modification anomalies) : 정확하지 않거나 일부의 튜플만 갱신되어 정보가 모호해지거나 일관성이 없어져 정확한 정보 파악이 되지 않는 문제점을 말합니다.

2. 그래서 정규화란?

관계형 데이터베이스에서 중복을 최소화하기 위해 데이터를 구조화하는 작업입니다. 좀 더 구체적으로는 불만족스러운 나쁜 릴레이션의 애트리뷰트들을 나누어서 좋은 작은 릴레이션으로 분해하는 작업을 말합니다. 정규화 과정을 거치게 되면 정규형을 만족하게 됩니다. 정규형이란 특정 조건을 만족하는 릴레이션의 스키마의 형태를 말하며 제 1 정규형, 제 2 정규형, 제 3 정규형, … 등이 존재합니다.

2-1. ‘나쁜’ 릴레이션은 어떻게 파악할까요?

엔티티를 구성하고 있는 애트리뷰트 간에 함수적 종속성(Functional Dependency)을 판단합니다. 판단된 함수적 종속성은 좋은 릴레이션 설계의 정형적 기준으로 사용됩니다. 즉, 각각의 정규형마다 어떠한 함수적 종속성을 만족하는지에 따라 정규형이 정의되고, 그 정규형을 만족하지 못하는 정규형을 나쁜 릴레이션으로 파악합니다.

2-2. 함수적 종속성이란 무엇일까요?

함수적 종속성이란 애트리뷰트 데이터들의 의미와 애트리뷰트들 간의 상호 관계로부터 유도되는 제약조건의 일종입니다. X 와 Y 를 임의의 애트리뷰트 집합이라고 할 때, X 의 값이 Y 의 값을 유일하게(unique) 결정한다면 X 는 Y 를 함수적으로 결정한다라고 합니다.

예를 들어, 학생 테이블학번,이름,나이가 있다고 했을 때, 학번을 알면 이름, 나이를 알 수 있으며, 학번이 다르면 그에 따른 값도 달라집니다. 이 경우에 이름,나이학번함수적 종속이라고 합니다. 기호로는 학번->이름, 학번->나이 로 표한할 수 있습니다.

함수적 종속성은 실세계에서 존재하는 애트리뷰트들 사이의 제약조건으로부터 유도됩니다. 또한 각종 추론 규칙에 따라서 애트리뷰트들간의 함수적 종속성을 판단할 수 있습니다. cf> 애트리뷰트들의 관계로부터 추론된 함수적 종속성들을 기반으로 추론 가능한 모든 함수적 종속성들의 집합을 폐포라고 합니다.

2-3. 각각의 정규형은 어떠한 조건을 만족해야 할까요?

  1. 분해의 대상인 분해 집합 D 는 무손실 조인 을 보장해야 합니다.
  2. 분해 집합 D 는 함수적 종속성을 보존해야 합니다.

제 1 정규형

애트리뷰트의 도메인이 오직 원자값만을 포함하고, 튜플의 모든 애트리뷰트가 도메인에 속하는 하나의 값을 가져야 합니다. 즉, 복합 애트리뷰트, 다중값 애트리뷰트, 중첩 릴레이션 등 비 원자적인 애트리뷰트들을 허용하지 않는 릴레이션 형태를 말합니다.

제 2 정규형

모든 비주요 애트리뷰트들이 주요 애트리뷰트에 대해서 완전 함수적 종속이면 제 2 정규형을 만족한다고 볼 수 있습니다. 완전 함수적 종속이란 X -> Y 라고 가정했을 때, X 의 어떠한 애트리뷰트라도 제거하면 더 이상 함수적 종속성이 성립하지 않는 경우를 말합니다.

예를 들어, 학번(Key), 과목코드(Key), 성적, 학부, 등록금이 있다고 했을 때, 함수적 종속성을 살펴보면 {학번,과목코드} -> 성적, {학번,과목코드} -> 학부, {학번,과목코드} -> 등록금처럼 {학번,과목코드}조합의 기본키로만 속성을 결정지을 수 있는 것을 완전 함수적 종속이라고 하고,
학번 -> 학부, 학번 -> 등록금 처럼 {학번,과목코드}조합의 기본키의 일부에도 함수적으로 종속되는 것을 부분 함수적 종속이라고 합니다.

여기서 제 2정규형은 완전 함수적 종속이 되어야 하므로 부분 함수적 종속부분인 학번, 학부, 등록금을 분리하여 학번(key), 학부, 등록금 테이블과 학번(key), 과목코드(key), 성적테이블로 만들어 부분 함수적 종속을 제거하는 것입니다.

제 3 정규형

어떠한 비주요 애트리뷰트도 기본키에 대해서 이행적으로 종속되지 않으면 제 3 정규형을 만족한다고 볼 수 있습니다. 이행 함수적 종속이란 X - >Y, Y -> Z의 경우에 의해서 추론될 수 있는 X -> Z의 종속관계를 말합니다. 즉, 비주요 애트리뷰트가 비주요 애트리뷰트에 의해 종속되는 경우가 없는 릴레이션 형태를 말합니다.

위의 학번(key), 학부, 등록금 테이블을 예로 들면, 학번 -> 학부, 학부 -> 등록금, 학번 -> 등록금이기 때문에 학번, 학부테이블과 학부, 등록금테이블로 나누어 주면 이행적으로 종속되지 않습니다.

BCNF(Boyce Codd Normal Form) 정규형

모든 결정자가 Key인 경우 BCNF라고 할 수 있습니다. 여러 후보 키가 존재하는 릴레이션에 해당하는 정규화 내용입니다. 복잡한 식별자 관계에 의해 발생하는 문제를 해결하기 위해 제 3 정규형을 보완하는데 의미가 있습니다. 비주요 애트리뷰트가 후보키의 일부를 결정하는 것을 분해하는 과정을 말합니다.

각 정규형은 그의 선행 정규형보다 더 엄격한 조건을 갖습니다.

  • 모든 제 2 정규형 릴레이션은 제 1 정규형을 갖습니다.
  • 모든 제 3 정규형 릴레이션은 제 2 정규형을 갖습니다.
  • 모든 BCNF 정규형 릴레이션은 제 3 정규형을 갖습니다.

수많은 정규형이 있지만 관계 데이터베이스 설계의 목표는 각 릴레이션이 3NF(or BCNF)를 갖게 하는 것입니다.

img

위 릴레이션은 1NF 는 만족한다는 가정하에,

  • 기본키가 아닌 모든 속성이 기본키에 완전 함수 종속이므로 2NF 를 만족합니다.
  • 기본키가 아닌 모든 속성이 기본키에 이행적 함수 종속이 되지 않으므로 3NF 를 만족합니다.

하지만, 결정자인 C 가 슈퍼키가 아닙니다. 그러므로 BCNF 를 위반합니다.

3. 정규화에는 어떠한 장점이 있을까요?

  1. 데이터베이스 변경 시 이상 현상(Anomaly) 제거 :위에서 언급했던 각종 이상 현상들이 발생하는 문제점을 해결할 수 있습니다.
  2. 데이터베이스 구조 확장 시 재 디자인 최소화 : 정규화된 데이터베이스 구조에서는 새로운 데이터 형의 추가로 인한 확장 시, 그 구조를 변경하지 않아도 되거나 일부만 변경해도 됩니다. 이는 데이터베이스와 연동된 응용 프로그램에 최소한의 영향만을 미치게 되며 응용프로그램의 생명을 연장시킵니다.
  3. 사용자에게 데이터 모델을 더욱 의미있게 제공 : 정규화된 테이블들과 정규화된 테이블들간의 관계들은 현실 세계에서의 개념들과 그들간의 관계들을 반영합니다.

4. 단점은 없을까요?

릴레이션의 분해로 인해 릴레이션 간의 연산(JOIN 연산)이 많아집니다. 이로 인해 질의에 대한 응답 시간이 느려질 수 있습니다. 조금 덧붙이자면, 정규화를 수행한다는 것은 데이터를 결정하는 결정자에 의해 함수적 종속을 가지고 있는 일반 속성을 의존자로 하여 입력/수정/삭제 이상을 제거하는 것입니다. 데이터의 중복 속성을 제거하고 결정자에 의해 동일한 의미의 일반 속성이 하나의 테이블로 집약되므로 한 테이블의 데이터 용량이 최소화되는 효과가 있습니다. 따라서 정규화된 테이블은 데이터를 처리할 때 속도가 빨라질 수도 있고 느려질 수도 있는 특성이 있습니다.

5. 단점에서 미루어보았을 때 어떠한 상황에서 정규화를 진행해야 할까요? 단점에 대한 대응책은?

조희를 하는 SQL 문장에서 조인이 많이 발생하여 이로 인한 성능저하가 나타나는 경우에 반정규화를 적용하는 전략이 필요합니다.

반정규화(De-normalization, 비정규화)

반정규화정규화된 엔티티, 속성, 관계를 시스템의 성능 향상 및 개발과 운영의 단순화를 위해 중복 통합, 분리 등을 수행하는 데이터 모델링 기법 중 하나입니다. 디스크 I/O 량이 많아서 조회 시 성능이 저하되거나, 테이블끼리의 경로가 너무 멀어 조인으로 인한 성능 저하가 예상되거나, 칼럼을 계산하여 조회할 때 성능이 저하될 것이 예상되는 경우 반정규화를 수행하게 됩니다. 일반적으로 조회에 대한 처리 성능이 중요하다고 판단될 때 부분적으로 반정규화를 고려하게 됩니다.

5-1. 무엇이 반정규화의 대상이 될까요?

  1. 자주 사용되는 테이블에 액세스하는 프로세스의 수가 가장 많고, 항상 일정한 범위만을 조회하는 경우
  2. 테이블에 대량 데이터가 있고 대량의 범위를 자주 처리하는 경우, 성능 상 이슈가 있을 경우
  3. 테이블에 지나치게 조인을 많이 사용하게 되어 데이터를 조회하는 것이 기술적으로 어려울 경우

5-2. 반정규화 과정에서 주의할 점은?

반정규화를 과도하게 적용하다 보면 데이터의 무결성이 깨질 수 있습니다. 또한 입력, 수정, 삭제의 질의문에 대한 응답 시간이 늦어질 수 있습니다.

Transaction

트랜잭션(Transaction)이란?

트랜잭션은 작업의 완전성 을 보장해주는 것입니다. 즉, 논리적인 작업 셋을 모두 완벽하게 처리하거나 또는 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능입니다. 사용자의 입장에서는 작업의 논리적 단위로 이해를 할 수 있고 시스템의 입장에서는 데이터들을 접근 또는 변경하는 프로그램의 단위가 됩니다.

트랜잭션과 Lock

잠금(Lock)트랜잭션은 서로 비슷한 개념 같지만 사실 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 데이터의 정합성을 보장하기 위한 기능입니다. 잠금은 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할을 합니다. 여기서 자원은 레코드나 테이블을 말합니다. 이와는 조금 다르게 트랜잭션은 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합되었을 때만 의미있는 개념은 아닙니다. 트랜잭션은 하나의 논리적인 작업 셋 중 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장하는 것입니다. 예를 들면 HW 에러 또는 SW 에러와 같은 문제로 인해 작업에 실패가 있을 경우, 특별한 대책이 필요하게 되는데 이러한 문제를 해결하는 것입니다.

트랜잭션의 특성

트랜잭션은 어떠한 특성을 만족해야할까요? Transaction 은 다음의 ACID 라는 4 가지 특성을 만족해야 합니다.

원자성(Atomicity)

만약 트랜잭션 중간에 어떠한 문제가 발생한다면 트랜잭션에 해당하는 어떠한 작업 내용도 수행되어서는 안되며 아무런 문제가 발생되지 않았을 경우에만 모든 작업이 수행되어야 합니다.

일관성(Consistency)

트랜잭션이 완료된 다음의 상태에서도 트랜잭션이 일어나기 전의 상황과 동일하게 데이터의 일관성을 보장해야 합니다.

고립성(Isolation)

각각의 트랜잭션은 서로 간섭없이 독립적으로 수행되어야 합니다.

지속성(Durability)

트랜잭션이 정상적으로 종료된 다음에는 영구적으로 데이터베이스에 작업의 결과가 저장되어야 합니다.

트랜잭션의 상태

트랜잭션 상태 다이어그램

Active

트랜잭션의 활동 상태. 트랜잭션이 실행중이며 동작중인 상태를 말합니다.

Failed

트랜잭션 실패 상태. 트랜잭션이 더이상 정상적으로 진행 할 수 없는 상태를 말합니다.

Partially Committed

트랜잭션의 Commit 명령이 도착한 상태. 트랜잭션의 commit이전 sql문이 수행되고 commit만 남은 상태를 말합니다.

Committed

트랜잭션 완료 상태. 트랜잭션이 정상적으로 완료된 상태를 말한다.

Aborted

트랜잭션이 취소 상태. 트랜잭션이 취소되고 트랜잭션 실행 이전 데이터로 돌아간 상태를 말합니다.

Partially Committed 와 Committed 의 차이점

Commit 요청이 들어오면 상태는 Partially Committed 상태가 됩니다. 이후 Commit을 문제없이 수행할 수 있으면 Committed 상태로 전이되고, 만약 오류가 발생하면 Failed 상태가 됩니다. 즉, Partially CommittedCommit 요청이 들어왔을때를 말하며, CommittedCommit을 정상적으로 완료한 상태를 말합니다.

트랜잭션을 사용할 때 주의할 점

트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋습니다. 즉 트랜잭션의 범위를 최소화하라는 의미입니다. 일반적으로 데이터베이스 커넥션은 개수가 제한적입니다. 그런데 각 단위 프로그램이 커넥션을 소유하는 시간이 길어진다면 사용 가능한 여유 커넥션의 개수는 줄어들게 됩니다. 그러다 어느 순간에는 각 단위 프로그램에서 커넥션을 가져가기 위해 기다려야 하는 상황이 발생할 수도 있는 것입니다.

교착상태(DeadLock)

교착상태란?

복수의 트랜잭션을 사용하다보면 교착상태가 일어날수 있습니다. 교착상태란 두 개 이상의 트랜잭션이 특정 자원(테이블 또는 행)의 잠금(Lock)을 획득한 채 다른 트랜잭션이 소유하고 있는 잠금을 요구하면 아무리 기다려도 상황이 바뀌지 않는 상태가 되는데, 이를 교착상태라고 합니다.

교착상태의 예(MySQL)

MySQL MVCC에 따른 특성 때문에 트랜잭션에서 갱신 연산(Insert, Update, Delete)를 실행하면 잠금을 획득합니다. (기본은 행에 대한 잠금)

classic deadlock 출처: https://darkiri.wordpress.com/tag/sql-server/

트랜잭션 1이 테이블 B의 첫번째 행의 잠금을 얻고 트랜잭션 2도 테이블 A의 첫번째 행의 잠금을 얻었다고 가정합니다.

Transaction 1> create table B (i1 int not null primary key) engine = innodb;
Transaction 2> create table A (i1 int not null primary key) engine = innodb;

Transaction 1> start transaction; insert into B values(1);
Transaction 2> start transaction; insert into A values(1);

트랜잭션을 commit 하지 않은채 서로의 첫번째 행에 대한 잠금을 요청하면

Transaction 1> insert into A values(1);
Transaction 2> insert into B values(1);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Deadlock 이 발생합니다. 일반적인 DBMS는 교착상태를 독자적으로 검출해 보고합니다.

교착 상태의 빈도를 낮추는 방법

  • 트랜잭션을 자주 커밋합니다.
  • 정해진 순서로 테이블에 접근합니다. 위에서 트랜잭션 1이 테이블 B -> A 의 순으로 접근했고, 트랜잭션 2는 테이블 A -> B의 순으로 접근했습니다. 트랜잭션들이 동일한 테이블 순으로 접근하게 합니다.
  • 읽기 잠금 획득 (SELECT ~ FOR UPDATE)(동시성 제어를 위하여 특정 데이터에 대해 Lock을 거는 기능)의 사용을 피합니다.
  • 한 테이블의 복수 행을 복수의 연결에서 순서 없이 갱신하면 교착상태가 발생하기 쉽습니다, 이 경우에는 테이블 단위의 잠금을 획득해 갱신을 직렬화 하면 동시성을 떨어지지만 교착상태를 회피할 수 있습니다.

Statement vs PreparedStatement

우선 속도 면에서 PreparedStatement가 빠르다고 알려져 있습니다. 이유는 쿼리를 수행하기 전에 이미 쿼리가 컴파일 되어 있으며, 반복 수행의 경우 미리 컴파일된 쿼리를 통해 수행이 이루어지기 때문입니다.

PreparedStatement에는 보통 변수를 설정하고 바인딩하는 static sql이 사용되고 Statement에서는 쿼리 자체에 조건이 들어가는 dynamic sql이 사용됩니다. PreparedStatement가 파싱 타임을 줄여주는 것은 분명하지만 static sql을 사용하는데 따르는 퍼포먼스 저하를 고려하지 않을 수 없습니다.

하지만 성능을 고려할 때 시간 부분에서 가장 큰 비중을 차지하는 것은 테이블에서 레코드(row)를 가져오는 과정이고 SQL 문을 파싱하는 시간은 이 시간의 10 분의 1 에 불과합니다. 그렇기 때문에 SQL Injection 등의 문제를 보완해주는 PreparedStatement를 사용하는 것이 좋습니다.

NoSQL

정의

관계형 데이터 모델을 지양 하며 대량의 분산된 데이터를 저장하고 조회하는 데 특화되었으며 스키마 없이 사용 가능하거나 느슨한 스키마를 제공하는 저장소를 말합니다.

종류마다 쓰기/읽기 성능 특화, 2 차 인덱스 지원, 오토 샤딩(DBMS 레벨에서 데이터를 나누는 것이 아니고 데이터베이스 자체를 분할하는 방식) 지원 같은 고유한 특징을 가집니다. 대량의 데이터를 빠르게 처리하기 위해 메모리에 임시 저장하고 응답하는 등의 방법을 사용합니다. 동적인 스케일 아웃(서버를 여러 대 추가하여 시스템을 확장하는 방법)을 지원하기도 하며, 가용성을 위하여 데이터 복제 등의 방법으로 관계형 데이터베이스가 제공하지 못하는 성능과 특징을 제공합니다.

CAP 이론

1. 일관성(Consistency)

일관성은 동시성 또는 동일성이라고도 하며 다중 클라이언트에서 같은 시간에 조회하는 데이터는 항상 동일한 데이터임을 보증하는 것을 의미합니다. 이것은 관계형 데이터베이스가 지원하는 가장 기본적인 기능이지만 일관성을 지원하지 않는 NoSQL 을 사용한다면 데이터의 일관성이 느슨하게 처리되어 동일한 데이터가 나타나지 않을 수 있습니다. 느슨하게 처리된다는 것은 데이터의 변경을 시간의 흐름에 따라 여러 노드에 전파하는 것을 말합니다. 이러한 방법을 최종적으로 일관성이 유지된다고 하여 최종 일관성 또는 궁극적 일관성을 지원한다고 합니다.

각 NoSQL 들은 분산 노드 간의 데이터 동기화를 위해서 두 가지 방법을 사용합니다. 첫번째로 데이터의 저장 결과를 클라이언트로 응답하기 전에 모든 노드에 데이터를 저장하는 동기식 방법이 있습니다. 그만큼 느린 응답시간을 보이지만 데이터의 정합성을 보장합니다. 두번째로 메모리나 임시 파일에 기록하고 클라이언트에 먼저 응답한 다음, 특정 이벤트 또는 프로세스를 사용하여 노드로 데이터를 동기화하는 비동기식 방법이 있습니다. 빠른 응답시간을 보인다는 장점이 있지만, 쓰기 노드에 장애가 발생하였을 경우 데이터가 손실될 수 있습니다.

2. 가용성(Availability)

가용성이란 모든 클라이언트의 읽기와 쓰기 요청에 대하여 항상 응답이 가능해야 함을 보증하는 것이며 내고장성이라고도 합니다. 내고장성을 가진 NoSQL 은 클러스터 내에서 몇 개의 노드가 망가지더라도 정상적인 서비스가 가능합니다.

몇몇 NoSQL 은 가용성을 보장하기 위해 데이터 복제(Replication)을 사용합니다. 동일한 데이터를 다중 노드에 중복 저장하여 그 중 몇 대의 노드가 고장나도 데이터가 유실되지 않도록 하는 방법입니다. 데이터 중복 저장 방법에는 동일한 데이터를 가진 저장소를 하나 더 생성하는 Master-Slave 복제 방법과 데이터 단위로 중복 저장하는 Peer-to-Peer 복제 방법이 있습니다.

3. 네트워크 분할 허용성(Partition tolerance)

분할 허용성이란 지역적으로 분할된 네트워크 환경에서 동작하는 시스템이 두 지역 간의 네트워크가 단절되거나 네트워크 데이터의 유실이 일어나더라도 각 지역 내의 시스템은 정상적으로 동작해야 함을 의미합니다.

저장 방식에 따른 NoSQL 분류

Key-Value Model, Document Model, Column Model, Graph Model로 분류할 수 있습니다.

1. Key-Value Model

가장 기본적인 형태의 NoSQL 이며 키 하나로 데이터 하나를 저장하고 조회할 수 있는 단일 키-값 구조를 갖습니다. 단순한 저장구조로 인하여 복잡한 조회 연산을 지원하지 않습니다. 또한 고속 읽기와 쓰기에 최적화된 경우가 많습니다. 사용자의 프로필 정보, 웹 서버 클러스터를 위한 세션 정보, 장바구니 정보, URL 단축 정보 저장 등에 사용합니다. 하나의 서비스 요청에 다수의 데이터 조회 및 수정 연산이 발생하면 트랜잭션 처리가 불가능하여 데이터 정합성을 보장할 수 없습니다. ex) Redis

2. Document Model

키-값 모델을 개념적으로 확장한 구조로 하나의 키에 하나의 구조화된 문서를 저장하고 조회합니다. 논리적인 데이터 저장과 조회 방법이 관계형 데이터베이스와 유사합니다. 키는 문서에 대한 ID 로 표현됩니다. 또한 저장된 문서를 컬렉션으로 관리하며 문서 저장과 동시에 문서 ID 에 대한 인덱스를 생성합니다. 문서 ID 에 대한 인덱스를 사용하여 O(1) 시간 안에 문서를 조회할 수 있습니다.

대부분의 문서 모델 NoSQL 은 B 트리 인덱스를 사용하여 2 차 인덱스를 생성합니다. B 트리는 크기가 커지면 커질수록 새로운 데이터를 입력하거나 삭제할 때 성능이 떨어지게 됩니다. 그렇기 때문에 읽기와 쓰기의 비율이 7:3 정도일 때 가장 좋은 성능을 보입니다. 중앙 집중식 로그 저장, 타임라인 저장, 통계 정보 저장 등에 사용됩니다. ex) MongoDB

3. Column Model

하나의 키에 여러 개의 컬럼 이름과 컬럼 값의 쌍으로 이루어진 데이터를 저장하고 조회합니다. 모든 컬럼은 항상 타임 스탬프 값과 함께 저장됩니다.

구글의 빅테이블이 대표적인 예로 차후 컬럼형 NoSQL 은 빅테이블의 영향을 받았습니다. 이러한 이유로 Row key, Column Key, Column Family 같은 빅테이블 개념이 공통적으로 사용됩니다. 저장의 기본 단위는 컬럼으로 컬럼은 컬럼 이름과 컬럼 값, 타임스탬프로 구성됩니다. 이러한 컬럼들의 집합이 로우(Row)이며, 로우키(Row key)는 각 로우를 유일하게 식별하는 값입니다. 이러한 로우들의 집합은 키 스페이스(Key Space)가 됩니다.

대부분의 컬럼 모델 NoSQL 은 쓰기와 읽기 중에 쓰기에 더 특화되어 있습니다. 데이터를 먼저 커밋로그와 메모리에 저장한 후 응답하기 때문에 빠른 응답속도를 제공합니다. 그렇기 때문에 읽기 연산 대비 쓰기 연산이 많은 서비스나 빠른 시간 안에 대량의 데이터를 입력하고 조회하는 서비스를 구현할 때 가장 좋은 성능을 보입니다. 채팅 내용 저장, 실시간 분석을 위한 데이터 저장소 등의 서비스 구현에 적합합니다.


참고 : https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database#index

https://3months.tistory.com/193

https://dodo000.tistory.com/20

https://yaboong.github.io/database/2018/03/10/database-normalization-2/

댓글남기기