Basic MySql

유웅조·2020년 5월 8일
1

Basic

목록 보기
9/13

RDBMS

RDBMS란 Relation Database Management System의 약자로 말그대로 관계형 데이터베이스를 유지, 관리하는데 사용되는 소프트웨어 시스템이다. 그리고 유지, 관리를 위해 SQL(Structured Query Language)를 사용한다. 그렇다면 RDM는 무엇일까 RDB는 데이터의 관계형 모델을 기반으로 한 데이터베이스이다. 데이터의 관계형 모델이란, 데이터 및 쿼리를 지정하기 위해 선언적 방법을 통해 필요한 방법을 직접적으로 명시하고 관계로 그룹지어 구조화해서 효과적으로 데이터 저장 및 검색 절차를 수행한다. 대부분의 관계형 데이터베이스는 SQL 데이터 정의 및 쿼리 언어를 사용한다. 보통 SQL 데이터베이스 스키마의 테이블은 술어 변수에 해당하고, 테이블의 내용은 관계, 키 제약 조건, 기타 제약 조건을 나타내고, SQL 쿼리문은 술어에 해당한다.

관계형 데이터베이스라는 말은 1970년에 처음으로 등장했다. E.F.Codd는 그의 논문 "A Relational Model of Data for Large Shared Data Banks"에서 이 용어를 사용했는데, 이 논문과 이후의 논문에서 그는 "관계형"이라는 의미를 적립시키고 12가지의 규칙을 만들어낸다. 하지만 실제 상접적으로 관계형 데이터베이스를 사용하고 구현하다보니 Codd의 규칙들과는 달라지게 되었고, 점차 광범위한 의미에서 관계형 데이터베이스 시스템을 의미하게 되었고 이는 다음과 같은 규칙들을 말한다.

  1. 행과 열이 있는 테이블의 형태를 가진 데이터를 관계로 나타낸다.
  2. 관계 연산자들을 제공해서 표를 조작할 수 있도록 한다.

RDBMS가 제공하는 주요 기능들을 살펴보면 다음과 같다.

1. 모든 데이터는 RDBMS가 제공하는 테이블에 저장된다.
2. 모든 데이터는 행과 열의 형태로 저장된다.
3. Primay Key를 제공해 각 열에 대한 고유 번호를 생성한다.
4. 인덱스를 만들어 빠른 속도로 데이터를 읽어들일 수 있다.
5. 2개 이상의 테이블이 공유하는 컬럼을 만드는 것이 가능하다.
6. 한명의 사용자가 다수의 사용자의 접근 권한을 제어하는 것이 가능하다.
7. 가상 테이블을 만들어 민감한 데이터를 저장하거나, 쿼리를 단순화하는 것이 가능하다.

관계형 데이터베이스는 데이터의 구조가 장점 중의 하나이다. 앞서 말했듯이 행과 열로 구분된 테이블 형식으로 데이터를 저장하기 때문에 사용자가 데이터를 이해하기 수월하다. 따라서 데이터베이스 쿼리를 통해 여러가지 연산을 훨씬 직관적으로 할 수 있다.

관계형 데이터베이스 관리 시스템은 소프트웨어 프로그램이다. 네트워크를 통해 접속하고 데이터베이스를 사용할 수 있도록 되어 있기 때문에 사용자에게 편의성과 데이터베이스의 안정성을 제공한다.

사실 RDBMS는 가장 빠른 데이터베이스 관리 시스템은 아니다. 하지만 시스템이 제공하는 쉽고 간단한 기능 덕에 속도가 덜 문제가 되는 편이다.
뿐만 아니라 RDBMS에 있는 최적화 기능들 덕에 퍼포먼스도 우수하여 어플리케이션, 데이터셋에 대한 수행을 훨씬 빠르게 수행하도록 한다. 또한 이는 자연스레 메모리와 비용 절감으로 이어지고 프로세스의 속도를 증가시켜 준다.

이외에도 데이터베이스를 유지, 관리함에 있어서의 용이함이라던가, 해당 데이터베이스에 대한 사용자 관리 등의 여러가지 편의성에 있어서 RDMBS는 강점을 보인다.

반면 RDBMS는 간단한 데이터 구조임에도 시스템의 데이터 구조에 맞게 집어 넣어야 하기 때문에 막상 현실 세계의 그것보다 훨씬 복잡해질 수 있다. 또한 현실에서 충분히 있을 법한 복잡한 타입의 데이터들, 예를 들면 그림과 같은 데이터를 저장하는 것이 많이 힘들다. 더 나아가 훨씬 복잡하고 비정형화된 데이터들을 저장하는 것에 상당한 제약이 따른다.

이런 문제를 어느 정도 해소하기 위해 NoSQL이 있다. NoSQL은 Not Only SQL이라는 의미이다. 빅데이터 시대로 넘어가면서 데이터의 규모는 어마어마한 크기가 되어 가고 있다. 이러한 데이터의 홍수 속에서 RDBMS 방식은 한계를 가질 수 밖에 없게되는데 이때 등장한 것이 바로 NoSQL이다.

관계형 데이터베이스가 데이터 사이의 관계를 긴밀히 연결시키는데 그 중심이 있다면, NoSQL은 단순히 SQL을 활용하는 형태가 아닌 다른 방식의 저장 기술을 이야기한다.

MongoDB는 문서 지향 데이터베이스로서 RDBMS의 행 개념보다 문서의 개념에 더 가깝다. 때문에 매우 유연하고 확장성이 높은데, 내장 문서와 배열 같은 표현이 가능해서 복잡한 객체의 계층 관계를 하나의 열로 표현할 수 있다.

MongoDB의 특징 중 가장 유명한 것은 스키마가 없다는 것이다. 이것은 필요할 때마다 필드를 추가하거나 제거하는 것이 매우 쉽다는 것을 의미한다. 따라서 개발 과정이 매우 단순하고 빠른 속도를 내는 것이 가능하다.

MongoDB는 Scale Out에 최적화되어 있다. 예를 들어, 많은 양의 로그가 쌓인다면 DB가 알아서 문서를 재분배하고 라우팅까지 완벽하게 처리한다.

MongoDB는 범용 데이터베이스 목적으로 만들어졌다. 그렇기 때문에 CRUD의 작업 외에도 다양한 기능을 제공한다.

MySQL 엔진에는 어떤 것들이 있을까

스토리지 엔진

MySQL 서버는 크게 MySQL 엔진과 스토리지 엔진으로 나눌 수 있다.

먼저 스토리지 엔진은 실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로부터 데이터를 읽어오는 기능을 수행한다. 하나의 MySQL 서버에서도 여러 개의 스토리지 엔진을 동시에 사용할 수 있고, 그 종류도 다양하다.

다음은 로컬 MySQL 서버의 스토리지 엔진이다.

+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
  1. ARCHIVE : 많은 양의 (참조하는 것이 거의 없는) 히스토릭한 정보, 보안에 대한 정보 같은 것들을 저장하거나 불러올 때 사용되는 인덱싱 되어 있지 않은 작은 테이블을 말한다.

  2. BLACKHOLE : 이 엔진은 데이터를 받기는 하지만 저장하지는 않는다. (Unix /dev/null, bit-bucket, null device). 쿼리가 항상 빈 set를 반환한다. 바이너리 로그가 활성화되어 있다면 쿼리문이 log된다. 그리고 슬레이브 서버로의 replicate가 가능하다. 이를 활용해서 슬레이브-사이드로 필터링 규칙을 보내야 하는데 모든 바이너리 로그 데이터가 너무 많은 트래픽을 발생시킬 경우, Blackhole을 통해 더미 슬레이브 프로세스를 설정해 실제 데이터는 저장하지 않고 필터링이 된 바이너리 로그를 기록할 수 있다.

  3. MRG_MYISAM : MyISAM 테이블의 모음이다.

  4. FEDERATED : 여러 개의 물리적인 서버를 논리상 하나의 데이터베이스로 연결해주는 기능을 제공한다.

  5. MyISAM : 옛 ISAM 코드를 기반으로 더 많은 확장성을 갖고 있는 MySQL 5.5 버전 이전의 기본 스토리지 엔진이다. Transaction을 지원하지 않고 외래키도 지원하지 때문에 후에 참조 무결성과 더 높은 동시성을 보장하는 InnoDB 엔진으로 기본 스토리지 엔진이 바뀌었다. 읽기 속도가 빠르지만 table-level-lock이기 때문에 row 단위의 삭제와 수정 작업은 굉장히 복잡하다.

  6. InnoDB

    MySQL 5.5 버전 이후 기본 스토리지 엔진이다.

    DML 작업 시 ACID 모델을 따르며, commit, rollback, crash-recovery를 지원하는 transaction 기능을 제공한다.

    • Transaction

      Transaction은 데이터베이스 관리 시스템에서의 상호작용의 단위이다. 이론적으로 데이터베이스 시스템은 각각의 Transaction에 대해 원자성, 일관성, 독립성, 영구성을 보장한다. 이를 각 단어의 첫 글자를 따 ACID라고 한다.
      
      1. Atomicity 원자성
      
      Transaction의 연산은 데이터베이스에 모두 반영되든지 아니면 전혀 반영되지 않아야 한다.
      Transaction 내의 모든 명령은 반드시 완벽히 수행되어야 하며, 모두가 완벽히 수행되지 않고 어느하나라도 오류가 발생하면 Transaction 전부가 취소되어야 한다.
      
      2. Consistency 일관성
      
      Transaction이 그 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터 베이스 상태로 변환한다.
      시스템이 가지고 있는 고정요소는 Transaction 수행 전과 Transaction 수행 완료 후의 상태가 같아야 한다.
      
      3. Isolation 독립성
      
      둘 이상의 Transaction이 동시에 병행 실행되는 경우 어느 하나의 Transaction 실행 중에 다른 Transaction의 연산이 끼어들 수 없다.
      수행중인 Transaction은 완전히 완료될 때까지 다른 Transaction에서 수행 결과를 참조할 수 없다.
      
      4. Durability 영구성
      
      성공적으로 완료된 Transaction의 결과는 시스템이 고장나더라도 영구적으로 반영되어야 한다.
      하나의 Transaction은 반드시 Commit 되거나 Rollback 된다.
      
      먼저 Commit은 논리적 단위(Transaction)의 작업이 성공했을 때, Transaction 관리자에게 알려주는 연산을 말한다.
      
      다음으로 Rollback은 한개의 Transaction이 비정상적으로 종료되어 데이터베이스의 일관성을 깨뜨렸을 때, Transaction의 원자성을 구현하기 위해 여태까지 Transaction이 행한 모든 연산을 취소하는 연산이다. 즉, 해당 Transaction을 재시작하거나 폐기한다.
      
      ps. 다음의 명령은 자동 Commit되기 때문에 Rollback할 수 없다.
      DROP DATABASE
      DROP TABLE
      DROP
      ALTER TABLE

    InnoDB는 쿼리를 최적화하기 위해 Primay Keys를 기반으로 데이터를 배치한다. 각각의 테이블은 Primay Key 조회를 위해 I/O를 최소화하기 위한 clustered index라는 Primay Key 인덱스를 갖고 있다. 데이터의 일관성과 통일성을 위해 외래키 제약을 지원한다. 이를 사용해 생성, 수성, 삭제의 작업은 각각 다른 테이블에서 일관성을 유지하는지 항상 확인되어 진다.

7) MEMORY : 메모리는 공식적으로는 HEAP이다. 메모리 상에 특수 목적 테이블을 생성한다. 데이터는 충돌이나 하드웨어 이슈나 전력 이슈에 대해 취약하기 때문에 다른 테이블에서 가져온 읽기 전용 캐시 데이터이거나 일시적인 작업이 필요한 테이블을 사용하기 위해 메모리를 사용한다.

8) CSV : CSV 파일을 저장할 때 사용하는 엔진이다.

MySQL 엔진

MySQL 서버에서 요청된 SQL 구문을 분석하고 최적화하는 등 실제적으로 두뇌에 해당하는 처리를 수행하는 것은 MySQL 엔진이다. 크게 나누어 보면 Connection Handler(클라이언트로부터 접속, 쿼리 요청을 처리), SQL Parser, 전처리기, Optimizer로 구성되어 있다.

쿼리를 실행하는 관점에서 SQL Parser는 사용자 요청으로 들어온 쿼리 구문을 토큰(MySQL이 인식할 수 있는 최소 단위의 어휘나 기호)으로 분리해 트리 형태의 구조로 만들어낸다. 기본 문법 오류가 있다면 이 과정에서 발견되며 사용자에게 오류를 던진다.

전처리기는 파서 과정에서 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점이 있는지 확인한다. 각 토큰을 테이블 이름이나 칼럼 이름, 내장 함수 같은 개체를 매핑해 해당 객체의 존재 여부와 객체의 접근권한 등을 확인하는 과정을 이 단계에서 수행한다. 만약 실제 존재하지 않거나, 권한 상 사용할 수 없는 개체의 토큰은 이 단계에서 걸러진다.

옵티마이저는 사용자의 요청으로 들어온 쿼리 구문을 어떻게 하면 저렴한 비용으로 가장 빠르게 처리할지 결정한다. 실질적으로 DBMS의 두뇌에 해당한다고도 볼 수 있을만큼 중요하고 어떠헥 더 나은 선택을 유도하는가를 아는 것이 중요할 것이다.

MySQL 엔진의 옵티마이저가 수행의 방법을 선택했다면 그 다음은 위에서 정리했던 스토리지 엔진과의 실질적인 데이터 교류이다. 이 과정에서 사용하는 것이 Handler인데 이는 MySQL 서버의 가장 밑단에서 실행 엔진의 요청에 따라 디스크에 데이터를 저장하거나 쓰는 역할을 수행한다. 이때 어떠한 스토리지 엔진을 사용하느냐에 따라 InnoDB가 Handler가 될 수도 있고, MyISAM이 Handler가 될 수도 있다.

Handler에게 요청을 보내는 것은 실행 엔진이다. 이 실행 엔진은 옵티마이저의 수행 방식을 실질적으로 실행하는 엔진이다. 예를 들어, 옵티마이저가 GROUP BY를 처리하기 위해 임시 테이블을 사용하기로 결정했다고 가정할 경우, 아래와 같은 방식으로 작동한다.

	1. 실행 엔진은 Handler에게 임시 테이블을 만들라고 요청
	2. 실행 엔진은 WHERE 절에 일치하는 레코드를 읽어오라고 Handler에게 요청
	3. 읽어온 레코드들을 1번에서 준비한 임시 테이블로 저장하라고 Handler에게 요청
	4. 데이터가 준비된 임시 테이블에서 필요한 방식으로 데이터를 읽어 오라고 Handler에게 요청
	5. 최종적으로 실행 엔진은 결과를 사용자나 다른 모듈로 넘김

RDMBS에서 테이블의 인덱싱이란

인덱스란 데이터베이스 테이블의 컬럼의 값과 해당 레코드가 저장된 주소를 키/값 형태로 저장해두는 것을 말한다. 그래서 어떠한 값을 찾고자 할 때, 정렬되어 있는 인덱스를 통해 빠르게 컬럼의 값으로 찾아갈 수 있다. 여기서 중요한 것은 인덱스가 항상 정렬되어 있다는 것이다.

인덱스는 SortedList와 마찬가지로 저장되는 칼럼의 값을 이용해 항상 정렬된 상태를 유지한다. 반면 데이터 파일은 ArrayList와 같이 저장된 순서대로 별도의 정렬 없이 그대로 저장해 둔다. SortedList와 같은 방식으로 인덱스를 처리하기 때문에 새로 데이터가 저장될 때마다 항상 값을 정렬해야 하므로 처리하는 과정이 느려질 수 밖에 없다. 한마디로 인덱스가 많은 테이블일수록 INSERT, UPDATE, DELETE 구문의 처리 속도를 희생하고 SELECT 구문의 성능을 향상시킨다.

B-Tree

B-Tree는 컬럼의 원래 값을 변형시키지 않고 인덱스 구조체 내에서 항상 정렬된 상태를 유지하는 것을 특징으로 일반적인 용도에 적합한 알고리즘이며 인덱싱에 사용된지 가장 오래되었다. 여러가지 변형된 형태가 있는데 DBMS에서는 주로 B+-Tree, B*-Tree, 가 사용된다.

B-Tree는 트리 구조의 최상위에 하나의 "루트 노드"가 존재하고 그 하위에 자식 노드가 붙어 있는 형태이다. 가장 하위에 있는 노드를 "리프 노드"라고 하고 중간 노드를 "브랜치 노드"라고 한다. 루트 노드와 브랜치 노드는 해당 컬럼의 인덱스 레코드를 갖고 있으며 해당 레코드의 주소까지의 경로인 자식 노드의 주소를 갖고 있다. 그래서 리프 노드에 도달하게 되면 리프 노드는 항상 실제 데이터의 주소 값을 갖고 있기 때문에 실제 데이터 파일에 바로 접근해서 파일을 불러올 수 있게 된다.

B-Tree에서 인덱스 키값을 추가할 때는 스토리지 엔진에 따라 새로운 키 값이 즉시 인덱스에 저장되거나 그렇지 않을 수도 있다. 새로운 키 값이 저장될 때 위치를 결정 지으면서 리프 노드의 용량을 확인하고 분리할지를 결정하는데 만약 분리할 경우 상위 브랜치 노드까지 처리의 범위가 넓어지기 때문에 비용이 많이 든다. 그리고 키를 추가하는 작업이 끝날 때까지 클라이언트는 쿼리의 결과를 받지 못하고 기다리는 문제가 발생할 수 있지만, InnoDB 5.5 버전 이후에서는 INSERT, DELETE 등에 의한 인덱스 키 추가, 삭제 기능을 버퍼링해서 지연 처리할 수 있게 되었다.

INSERT, UPDATE, DELETE 작업을 할 때 이러한 인덱스 관리에 따르는 추가 비용을 감당하면서 인덱스를 구축하는 이유는 빠른 검색을 위해서다. 루트 노드부터 리프 노드까지의 트리 탐색은 SELECT 뿐만 아니라 UPDATE, DELETE를 처리하기 위해서도 적용이 가능하다. 또한 완전 일치, 부분 일치 검색도 가능하다.

InnoDB에서는 인덱스가 조금 더 까다로우며 중요할 수 있다. 그 이유는 InnoDB 테이블이 지원하는 레코드 잠금이나 넥스트 키 락이 검색을 수행한 후 인덱스를 잠근 후 테이블의 레코드를 잠그기 때문이다. 따라서 테이블에 적절히 사용할 수 있는 인덱스가 없으면 불필요하게 많은 레코드를 잠글 수 있다.

인덱스를 이용해 어떻게 실제 레코드를 읽어 내는지에 대해 대표적인 3가지 방법이 있다.

  1. 인덱스 레인지 스캔

이 방법은 가장 대표적인 방법으로 검색해야 할 인덱스의 범위가 결정됐을 때 사용하는 방식이다.

SELECT * FROM employees WHERE first_name BETWEEN 'Ebbe' AND 'Gad';

예를 들면 위와 같은 쿼리처럼 특정 영역이 정해져있는 경우, 사용된다. 일단 시작하는 위치를 찾으면 그때부터는 리프 노드의 레코드만 순서대로 스캔하다가 끝나는 위치에 다다르면 지금까지 읽어드린 레코드를 사용자에게 반환하고 쿼리를 끝마친다. 이때 레코드 한건 단위로 랜덤 I/O가 한번씩 실행된다. 그래서 인덱스를 통해 레코드를 읽는 작업은 비용이 많이 드는 작업으로 분류된다. 따라서 인덱스를 통해 읽어야할 데이터 레코드가 20~25%가 넘는다면 인덱스를 통한 읽기보다 테이블의 데이터를 직접 읽는 것이 더 효율적인 처리 방식이 될 것이다.

  1. 인덱스 풀 스캔

이 방법은 인덱스의 처음부터 끝까지 모두 읽는 방식을 말한다. 일반적으로 인덱스의 크기가 테이블의 크기보다 작기 때문에 인덱스에 명시된 컬럼만으로 쿼리문의 조건을 처리할 수 있는 경우 이 방식이 사용된다. 만약 데이터 레코드까지 모두 읽어야 한다면 이 방식으로 처리되지 않는다. 수행 방식으로는 우선 리프 노드의 제일 앞 또는 제일 뒤로 이동한뒤 각 리프 노드가 Linked List로 되어 있음을 활용해 모든 리프 노드를 처음부터 끝까지 스캔한다.

  1. 루스 인덱스 스캔

이 방법은 인덱스 레인지 스캔처럼 범위 사이를 스캔하지만 중간마다 필요하지 않은 인덱스 키값은 무시하고 다음으로 넘어가는 형태이다. 예를 들어, 아이디 1 ~ 5까지를 스캔한다면 1에 해당하는 모든 인덱스 키값을 읽지 않고 다음인 2로 넘어가는 방식이다. GROUP BY 또는 집합 함수 중에 MAX(), MIN() 함수에 대해 최적화를 하는 경우에 사용된다.

테이블의 정규화란 무엇일까

테이블 정규화라는 것은 모델에서 중복된 데이터를 최소화하고 일반적으로 납득될 수 있는 모델로 만드는 것이다. 크게 1~6단계까지 있으며 중간중간에 또 다른 정규화 규칙들이 있다.

논리 모델링에서는 데이터의 저장 비용 최소화를 위한 정규화를 진행한다면 물리 모델링에서는 데이터를 읽어 오는 비용을 최소화하는 반정규화를 진행하게 된다. 이 두가지를 적절히 활용해 가장 효율적으로 데이터를 관리하는 것이 중요하다.

  1. No Repeating Group

모든 속성은 반드시 하나의 값을 가져야 한다.

예를 들어 전화번호부에서 친구에 대한 테이블을 만드는데 우편번호와 전화번호를 저장한다고 하자. 그럴 때의 속성 값으로 "우편번호와전화번호"라는 것이 있다면 이것은 두 가지의 값이 한 개의 속성에 합쳐져 있는 셈이다. 따라서 각각 "우편번호", "전화번호"로 나누어 두 개의 속성으로 저장해야 할 것이다.

  1. Whole Key Dependent

식별자 일부에 종속되는 속성은 제거해야 한다.

어떤 엔터티의 식별자를 구성하는 속성이 2개일 때, 그 엔터티의 모든 속성이 식별자를 구성하는 두 개의 속성에 완전히 종속적인지를 확인해야 한다.

예를 들어 어떤 회원의 친구를 저장하는 테이블이 있다고 할 때, <친구>(회원 번호[FK], 친구회원번호[FK] | 친구회원이름) 과 같은 방식으로 테이블을 만들 경우, (친구회원이름)(친구회원번호[FK])라는 식별자에만 종속적이다. 따라서 회원 엔터티의 이름으로 이동해야 할 것이다.

위의 경우에서처럼 보통 이 규칙을 만족시키기 위해서는 새로운 엔터티가 생성되곤(혹은 원래 있던 엔터티일 경우 속성의 이동 혹은 통합) 한다. 하지만 나중에 물리 모델링이 완료되면 조회 성능의 향상을 위해 일부 속성을 복사해 두는 형태의 반정규화를 사용하게 되는 경우도 생긴다.

  1. Non-Key Independent

식별자 이외의 속성 간에 종속 관계가 존재하면 안 된다.

식별자가 아닌 모든 속성은 식별자에 종속성을 가져야 함과 동시에 식별자가 아닌 모든 속성 간에는 어떠한 종속 관계도 없이 모두 독립적이어야 한다.

예를 들어 앞서 <회원> 엔터티에 회원의 직업을 저장한다고 할 때, <회원>(회원번호 | 회원명, 직업코드, 직업명) 이와 같은 방식으로 저장할 수 있다. 하지만 이렇게 저장하게 되면 회원 엔터티에서 직업 코드를 관리하고 있다는 문제도 있지만 직업명이 직업 코드에 종속적이라는 문제도 있다. 따라서 <직업>(직업코드 | 직업명), <회원>(회원번호 | 회원명 직업코드[fk])로 변경할 수 있을 것이다.

길이를 알 수 없는 배열을 저장하려면 어떻게 해야할까

앞서 논리 모델링에서의 정규화에 대해 간략하게 알아보았다면 물리 모델링에서 가장 중요한 것은 적합한 데이터 타입을 선정하는 작업일 것이다. 데이터 타입을 선정할 때는 가능한 최대 길이 값을 기준으로 컬럼의 길이를 지정하는 것이 일반적이라고는 하지만 무분별하게 길이를 크게 선정한다면 디스크의 공간은 물론 메모리나 CPU 자원도 낭비하게 된다. 따라서 실제 저장되는 값의 성격을 정확히 파악해서 최적의 타입과 적절한 길이를 선정하는 것이 중요하다.

  1. 문자열 (CHAR, VARCHAR)

문자열 타입인 CHAR, VARCHAR의 가장 큰 차이점은 각각 고정적인 길이, 가변적인 길이를 가진다는 점이다.

CHAR(1)과 VARCHAR(1) 타입을 사용할 때 실제 사용되는 저장 공간은 1 ~ 3 바이트 정도이다. 하지만 VARCHAR 타입에 글자가 저장될 때 문자열의 길이를 관리하기 위한 1 ~ 2 바이트의 공간을 추가적으로 더 사용한다. 하나의 레코드에서 TEXT와 BLOB을 제외한 칼럼의 전체 크기는 65KB를 초과할 수 없다. 따라서 VARCHAR 타입의 최대 크기는 64KB 정도이다. 만약 이 크기를 넘어서는 데이터를 저장하려고 한다면 에러가 발생하거나 자동으로 TEXT 타입으로 변경된다.

저장해야하는 데이터가 가변적인가, 그렇지 않은가에 따라 CHAR과 VARCHAR를 구분해서 사용해야 한다. CHAR의 경우 예를 들어 CHAR(10) 이라면 실제 데이터가 10이하이더라도 항상 10에 대한 메모리를 확보해놓기 때문에 데이터가 자주 바뀌더라도 신속하게 대처할 수 있다. 하지만 VARCHAR(10)의 경우, 실제 데이터만큼의 메모리를 사용하고 있기 때문에 해당 레코드 전체를 다른 공간으로 이동시키거나 레코드의 다른 값들을 다른 공간으로 이동시켜야 하기 때문에 잦은 데이터 교체에 비효율적이다.

MySQL에서 CHAR, VARCHAR 뒤에 지정하는 숫자는 바이트를 의미하는 것이 아니라 문자의 수를 의미한다. 일반적으로 영어는 1글자 당 1바이트, 한국어나 일본어와 같은 아시아권 언어는 1글자 당 최대 2바이트를 사용한다. 이외 UTF-8의 문자는 최대 3바이트까지 사용될 수 있다.

  1. 숫자

숫자를 저장하는 타입은 크게 값의 정확도에 따라 나뉘어 진다. 먼저 참값은 소수점 이하 값의 유무에 관계없이 정확히 그 값을 그대로 유지하는 것을 의미한다. 데이터 타입으로는 INT, DECIMAL. 두번째로 근사값은 흔히 부동 소수점이라고 불리는 값을 의미하며, 처음 컬럼에 저장한 값과 조회된 값이 정확하게 일치하지 않고 최대한 비슷한 값을 관리하는 것을 의미한다. 데이터 타입으로는 FLOAT, DOUBLE.

또한 저장 포맷에 따라 십진, 이진 표기법으로도 나뉠 수 있다. MySQL의 INTEGER, BIGINT 등 대부분의 숫자 타입은 모두 이진 표기법을 사용한다. 십진 표기법은 DECIMAL 타입일 경우이며 금액처럼 소수점까지 정확하게 관리해야 하는 값을 저장할 때 사용한다. 또한 최대 65자리 숫자까지 저장할 수 있으므로 BIGINT로도 저장할 수 없는 값을 저장할 때 사용된다.

  • 정수

    타입필요 저장 공간저장 가능한 수의 범위(unsigned)
    TINYINT1 byte0 ~ 255
    SMALLINT2 byte0 ~ 65535
    MEDIUMINT3 byte0 ~ 16777215
    INTEGER4 byte0 ~ 4294967295
    BIGINT8 byte0 ~ 18446744073709551615

    정수 타입 뒤에 명시되는 괄호는 화면에 표시할 자리 수를 의미한다.

  • 부동 소수점

    부동 소수점을 저장하기 위해서는 FLOAT, DOUBLE 타입을 사용한다. 부동 소수점은 근사값을 저장하는 방식이기 때문에 동등 비교는 사용할 수 없다. 이외에도 MySQL 메뉴얼에 부동 소수점을 사용할 때의 주의 사항이 많이 있다.

  • DECIMAL

    부동 소수점은 유효 범위 이외의 값은 가변적이기 때문에 정확한 값을 보장할 수 없는 반면, 금액이나 대출 이자 등과 같이 고정된 소수점까지 정확하게 관리해야 할 때는 DECIMAL 타입을 사용하면 된다. 비슷한 성격으로 NUMERIC 타입도 있다.

    DECIMAL은 숫자 하나를 저장하는데 1/2 byte가 필요하다. 그렇기 때문에 저장하는 숫자의 자리수/2의 결과값을 올림 처리한 만큼의 바이트 수가 필요하다. 결과적으로 성능상으로, 공간 사용면에서 DECIMAL이 효율적인 편은 아니라고 할 수 있고 실제로 소수가 아닌 정수 값인 경우에 미세하지만 BITINT 타입의 연산이 더 빠르다.

  1. 날짜와 시간

    타입필요 저장 공간저장 가능한 값의 범위
    YEAR1 byteYEAR(2) : 70(1970) ~ 69(2069)
    YEAR(4) : 1901 ~ 2155
    TIME3 byte-839:59:59 ~ 838:59:59
    DATE3 byte1000-01-01 ~ 9999-12-31
    TIMESTAMP4 byte1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 (utc)
    DATETIME8 byte1000-01-01 00:00:00 ~ 9999-12-31 23:59:59

    DATETIME, DATE 타입은 클라이언트로부터 입력된 값을 그대로 저장하고 조회할 때도 변환없이 그대로 출력한다. 하지만 TIMESTAMP는 항상 UTC 타입존으로 저장되기 때문에 타임존이 달라지면 값이 자동으로 보정된다.

  2. ENUM, SET

  • ENUM

    ENUM 타입은 반드시 하나의 값만 저장이 가능하고 코드화된 값을 관리하는데 효율적이다. 내부적으로 숫자 값으로 매핑해서 관리한다. ENUM(orange, grape)라면 실제로는 1: orange, 2: grape와 같은 형태이다.

  • SET

    SET은 ENUM과 같이 문자열 값을 정수 값으로 매핑해서 저장한다. 다만 차이점은 하나의 컬럼에 여러 개의 값을 저장할 수 있다는 것이다.

  1. TEXT, BLOB

    MySQL에서 대량의 데이터를 저장하려면 TEXT나 BLOB 타입을 사용해야 한다. 이 두 타입은 거의 똑같은 설정이나 방식으로 작동한다. 유일한 차이점은 TEXT 타입은 문자열을 저장하는 대용량 컬럼이라서 문자집합이나 콜레이션을 가진다는 것이고, BLOB 타입은 이진 데이터 타입이라서 별도의 문자집합이나 콜레이션을 가지지 않는다는 것이다.

    타입필요 저장 공간
    (L = 저장하고자 하는 데이터의 바이트)
    저장 가능한 바이트
    TINYTEXT, TINYBLOBL + 1 byte255
    TEXT, BLOBL + 2 byte65,535
    MEDIUMTEXT, MEDIUMBLOBL + 3 byte16777,215
    LOGNTEXT, LONGBLOBL + 4 byte4,294,967,295

    MySQL에서 레코드의 전체 크기가 64KB를 넘지 않는다면 VARCHAR, VARBINARY의 길이는 제한이 없다. 그래서 레코드의 전체 크기가 64KB를 넘어선다면 TEXT나 BLOB으로 전환하는 것이 좋을 것이다.

    TEXT, BLOB은 MEMORY 엔진에서 지원하지 않기 때문에 임시 테이블을 생성해야 하는 쿼리라면 이 작업은 메모리가 아닌 디스크에서 수행될 것이고 그렇게 되면 처리 속도가 느려지는 것은 물론이고 다른 쿼리 작업에도 영향을 줄 수 있다. 그래서 TEXT, BLOB이 포함된 컬럼에는 SELECT * FROM ...과 같은 쿼리보다는 꼭 필요한 컬럼만 조회하는 것이 더 낫다.

    BLOB이나 TEXT 타입의 컬럼에 인덱스를 생성할 때는 컬럼 값의 몇 바이트까지 인덱스를 생성할 것인지를 명시해야 할 때도 있다.

  2. 이외에

    이외에도 공간 데이터를 관리하기 위한 Spatial 타입도 있다. GEOMETRY, POINT, LINESTRING, POLYGON 등이 있다.

마지막으로 길이를 알 수 없는 배열을 저장하기 위해서, 우선 데이터 타입 중 배열은 없기 때문에 다른 데이터 타입으로 바꾸어 저장해야 한다. 그렇다면 크게 두 가지일텐데 이진 데이터 혹은 문자열이 방법일 것이다. 거기다가 길이를 알 수 없다면 길이의 제한이 있어서도 안된다. 따라서 배열을 스트링이나 이진 데이터로 변환시킨 뒤, TEXT 혹은 BLOB 타입으로 저장하는 것이 좋을 것 같다.

Hash

해시는 해시 함수를 통해 얻어지는 값을 말하는데, 해시 함수는 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다.

보통 해시 테이블이라는 자료 구조에 사용되어 빠른 데이터 검색 기능을 제공한다. 또한 암호학에서 사용되기도 하는데 그 이유는 해시 함수의 결정론적인 작동 방식과 해시만을 이용해 원래의 입력 값을 알아내기 어렵기 때문이다.

암호학적 해시함수의 종류로는 MD5, SHA계열이 있다.

DB에 사용자의 암호를 평문으로 저장하지 않고도 사용자의 암호를 인증하는 것이 가능한 이유는 무엇일까

암호화 해시 함수는 일방 함수로 복호화가 불가능하다. 그렇기 때문에 해시된 값들을 비교해서 동일 비교가 가능하고, 만약 동일하지 않다면 원본값도 다르다는 결론을 내릴 수 있다. 이 특성을 이요해서 평문으로 사용자의 암호를 저장하지 않더라도 한번 저장된 암호의 해시값과 인증 요청이 들어온 사용자의 암호의 해시값이 같다면 인증을 진행할 수 있다.

하지만 이러한 방법만을 사용할 경우, 해시 함수의 장점인 빠른 검색 속도 때문에 보안상 매우 취약해질 수도 있는데, 그 이유는 해시 함수의 원본값에 대해 결과값이 항상 동일하기 때문이다. 예를 들어, 많은 정보에 대한 해시값을 확보해놓은 "Rainbow Table"을 구축해놓고 무차별 대입을 통해 일치하는 해시값을 찾아내기 시작하면 굉장히 빠른 속도로 원본값을 알아낼 수 있다.

이러한 문제를 보완하기 위해 등장한 것이 Salting 솔팅이라는 개념이다. 솔트는 말그대로 소금이라는 의미를 갖고 있다. 우리가 음식에 간을 더하기 위해 소금을 치듯, 임의의 데이터를 해싱할 때 일종의 소금과 같은 추가적인 데이터를 함께 해시하는 것을 의미한다. 이러한 방식을 사용하면 만약에 Rainbow Attack을 이용해 원본값을 알아낸다하더라도 정확한 원본을 파악하기 힘들다.

하지만 이 방법도 완벽한 해결 방법은 아니다. 그래서 Key Stretching을 추가적으로 이용한다. 솔트와 함께 임의의 데이터를 해싱하여 다이제스틀르 생성하고, 이것을 또다시 해시하고... 를 임의의 수만큼 반복한다. 따라서 원본값까지의 추적을 굉장히 어렵게 만든다.

  • SHA256

    SHA 256 해시 함수는 어떤 길이의 값을 입력하더라도 256비트의 고정된 결과값을 출력한다. 일반적으로 입력값이 조금만 변동해도 출력값이 완전히 달라지기 때문에 출력값을 토대로 입력값을 유추하는 것은 거의 불가능하다.

    SHA 256의 대략적인 작동 방식은 먼저 원본 데이터 뒤의 비트에 1을 추가한 뒤 그 길이가 512의 배수가 되도록 0을 추가하고 마지막 64비트에 원본 데이터의 bit의 길이를 적는다. 이렇게 512의 배수가 된 데이터를 512 bit의 단위로 쪼개서 여러 개의 chunk를 만들어 각 chunk마다 특정 연산을 수행해 최종적인 hash 값을 만든다.

0개의 댓글