강의 영상 링크

1. 데이터베이스

현실 세계의 특정 측면을 모델링하는 상호 연관된 데이터의 체계적 집합

2. 파일에 데이터를 저장한다면?

스포티파이 같은 서비스를 생각해보자.

아티스트와 아티스트가 발매한 앨범 정보를 저장해야 한다. 디비에는 아티스트 엔티티와 앨범 엔티티가 필요하다.

데이터를 csv와 같은 파일에 기록하여 DBMS가 관리한다고 해보자 (artists.csv, albums.csv)

아티스트에는 name, year, country 속성이 있고, 앨범에는 name, artist, year 속성이 있다.

"Wu-Tang Clan", 1992, "USA"
"Notorious BIG", 1992, "USA"
"GZA", 1990, "USA"

이런 방식의 데이터베이스에는 여러 문제가 있다.

  • Data Integrity
    • 앨범 연도를 유효하지 않은 스트링으로 쓴다면?
    • 앨범에 여러 아티스트가 존재한다면?
    • 앨범을 가진 아티스트를 삭제한다면?
  • Implementation
    • 특정 레코드를 어떻게 조회할 것인지?
    • 동일한 데이터베이스를 사용하는 새 애플리케이션을 만들고 싶다면?
    • 그렇게 만든 애플리케이션이 여러 머신에서 돌아가야 한다면?
    • 여러 스레드가 동일 파일에 동시에 기록하려 한다면?
  • Durability
    • 레코드 업데이트 중에 프로그램 크래시가 발생한다면?
    • High availability를 위해 여러 머신에 데이터베이스를 replicate하려 한다면?

이런 어려움으로 인해 데이터베이스를 관리하는 여러 방식이 등장하게 된다.

3. DBMS (Database Management System)

애플리케이션이 데이터베이스에 정보를 저장하고 분석할 수 있게 해주는 소프트웨어를 DBMS라 한다. 일반적인 DBMS는 어떤 데이터 모델에 맞게 데이터 정의, 수정, 쿼리, 업데이트, 관리를 할 수 있게 해준다.

데이터 모델은 데이터베이스의 데이터를 설명하기 위한 개념의 모음이다.

  • 관계형
  • NoSQL
  • Array / Matrix / Vector

스키마는 데이터 모델을 기반으로 특정 데이터에 대한 설명이다. 데이터 모델에 대한 데이터 구조를 정의하고, 이게 없으면 저장된 데이터는 그냥 무작위 비트와 다름없다.

초기의 DBMS는 논리 계층과 물리 계층이 강하게 연결되어 있어서 만들기도 어려웠고 유지하기도 어려웠다. 개발자들은 데이터베이스를 배포하기 전에 애플리케이션이 어떤 쿼리를 실행할지 대략적으로라도 미리 알고 있어야 했다.

논리 계층은 데이터베이스가 어떤 엔티티와 속성을 갖고 있는지를 보여주고, 물리 계층은 엔티티와 속성이 실제로 어떻게 저장되는지를 보여준다.

초기 DBMS에서 물리 계층은 추상화되지 않고 애플리케이션 코드에 작성되었다. 따라서 데이터 저장 방식을 바꾸려면 애플리케이션 코드를 수정해야 했다.

예를 들어 equality 조회가 많을 것으로 예상해 키-밸류 스토어를 해시맵으로 구현했다고 해보자. 그런데 나중에 보니 범위 스캔이 많은 것으로 결정된다면 애플리케이션 코드를 고쳐서 B-tree 구현을 추가하거나 해야한다.

4. 관계형 모델

1960년대에 Ted Codd는 사람들이 매번 물리 계층을 수정하기 위해 DBMS를 다시 작성하는 걸 보고 관계형 모델을 제안했다.

관계형 모델은 relation에 기반하여 데이터베이스 추상화를 했고, 주요 아이디어는 다음과 같다.

  • 데이터베이스를 단순한 자료 구조(Realation)에 저장.
  • 물리 계층은 DBMS 구현에 맡긴다.
  • 고수준 언어로 데이터에 접근하고 DBMS가 최적 실행 전략을 판단.

관계형 데이터 모델의 세 가지 핵심 개념은 다음과 같다.

  • 구조(Structure) : 물리적 저장 방식과는 독립적인 릴레이션과 그것의 내용 정의
  • 무결성(Integrity) : 데이터베이스가 특정 제약을 만족하도록 보장
  • 조작(Manipulation) : 데이터 접근 및 수정을 위한 프로그래밍 인터페이스

관계형 모델이 제공하는 중요한 아이디어는 데이터 독립성이다. 만약 유저/애플리케이션을 저수준 데이터 표현과 분리하면 다음 장점을 얻을 수 있다.

  • 유저는 고수준 애플리케이션 로직만 신경쓰면 된다.
  • DBMS는 OS, 데이터 내용, 작업 등에 맞춰서 최적화할 수 있다. 또한 이러한 요소들이 변화하더라도 다시 최적화할 수 있다.

다음은 관계형 모델에서 사용되는 몇 가지 개념들이다.

  • 릴레이션 :
    • 속성들 간의 관계를 나타내는 unordered set이다. 순서가 없기 때문에 어떻게든 저장하고 최적화할 여지를 남길 수 있다.
    • 릴레이션에는 중복된 항목이 있을 수 있다.
  • 튜플 :
    • 릴레이션 내에서의 속성값들의 집합.
  • n-ary 릴레이션 :
    • n개의 칼럼을 갖는 릴레이션
  • 기본 키 :
    • 테이블에서 단일 튜플을 식별하는 속성.
  • 외래 키 :
    • 한 릴레이션의 속성이 다른 관계의 튜플을 참조할 때 사용.
    • 일반적으로 다른 테이블의 기본 키를 가리킨다.
  • 제약 조건 :
    • 모든 데이터베이스 인스턴스에서 만족해야 하는 조건.
    • 고유 키외래 키 제약이 일반적.

5. Data Manipulation Languages (DMLs)

DML은 DBMS가 데이터 저장 및 조회를 위해 열어둔 API이다. 다음과 같이 두 가지 유형으로 나뉜다.

  • 절차적 언어(Procedural)
    • 쿼리에서 원하는 결과를 얻기 위해 DBMS가 수행해야 할 절차를 직접 명시하는 방식.
    • ex) for문을 통해 모든 레코드를 순회하고 레코드 수를 세는 방식
  • 비절차적/선언형 언어 (Non-Procedural/Declarative)
    • 어떤 데이터를 원하는지만 명시하고, 어떻게 찾을지는 명시하지 않는 방식

6. 관계 대수 (Relational Algebra)

릴레이션에서 튜플을 조회하고 조작하기 위한 기본 연산의 집합이다.

각 연산은 한 개 이상의 릴레이션을 인풋으로 받아서 새 릴레이션을 출력한다.

쿼리 작성을 위해 이러한 연산을 체이닝하여 복잡한 연산을 만들 수 있다.

Selection

릴레이션에서 조건(predicate)을 만족하는 튜플만 선택해서 출력한다.

  • Syntax : $\sigma_{\text{predicate}} (R)$
  • Example : $\sigma_{\text{a_id=’a2’}} (R)$
  • SQL : SELECT * FROM R WHERE a_id = 'a2'

Projection

릴레이션을 받아서 특정 속성만 가지고 있는 새 릴레이션을 출력한다. 속성의 순서를 바꿀 수도 있고 값을 가공할 수도 있다.

  • Syntax : $\pi_{\text{A1,A2,…,An}}(R)$
  • Example : $\pi_{\text{b_id-100,a_id}}(\sigma_{a_id=’a2’})$
  • SQL : `SELECT b_id-100, a_id FROM R WHERE a_id = ‘a2’

Union

두 릴레이션을 입력받아 양쪽 릴레이션 중 하나 이상에 포함된 튜플을 모두 포함하는 새 릴레이션을 출력한다. (릴레이션은 set이므로 중복은 제거된다. 하지만 SQL의 기본 UNION은 중복을 허용하여 처리하므로 대응되는 SQL 표현은 UNION ALL이다)

입력 릴레이션은 동일한 속성(스키마)를 가져야 한다.

  • Syntax : $(R \cup S)$
  • SQL : (SELECT * FROM R) UNION ALL (SELECT * FROM S)

Intersection

두 릴레이션에서 공통으로 존재하는 튜플만 포함하는 새 릴레이션을 출력한다.

입력 릴레이션은 동일한 속성(스키마)를 가져야 한다.

  • Syntax : $(R \cap S)$
  • SQL : (SELECT * FROM R) INTERSECT (SELECT * FROM S)

Difference

첫 릴레이션에는 있지만 두번째 릴레이션에는 없는 튜플만 포함하는 새 릴레이션을 출력한다.

입력 릴레이션은 동일한 속성(스키마)를 가져야 한다.

  • Syntax : $(R - S)$
  • SQL : `(SELECT * FROM R) EXCEPT (SELECT * FROM S)

Product

두 릴레이션의 모든 가능한 튜플 조합을 포함하는 새 릴레이션을 출력한다.

  • Syntax : $(R \times S)$
  • SQL :
    • (SELECT * FROM R) CROSS JOIN (SELECT * FROM S)
    • or (SELECT * FROM R, S)

Join

두 릴레이션을 입력으로 받아 공통 속성의 값이 같은 튜플들을 결합한 새 릴레이션을 출력한다.

  • Syntax : $(R \Join S)$
  • SQL : SELECT * FROM R JOIN S USING (ATTRIBUTE1, ATTRIBUTE2...)

주의할 점

관계 대수는 튜플을 조회하고 조작하는 기본 연산을 정의하고, 연산 순서를 통해 쿼리 수행 방식에 영향을 준다.

예를 들어, $\sigma_{\text{b_id=102}} (R \Join S)$는 $R$과 $S$를 조인한 다음 필터링하는 방식이고, $(R \Join (\sigma_{b_id=102}))$는 $S$에서 먼저 필터링하고 그 다음 그 결과를 $R$과 조인하는 방식이다.

두 statements의 결과는 같지만 $S$가 1B 튜플을 갖고 b_id=102인 튜플이 하나만 있다면 후자가 전자보다 압도적으로 빠를 것이다.

이러한 점 때문에 고수준의 결과를 기술하고 DBMS가 쿼리 계산 방식을 결정하게 하는 SQL 언어가 현재 관계형 DBMS 쿼리 작성 방식의 de facto가 되었다고 볼 수 있다.