본문 바로가기

Backend/Database

MySQL의 문자열 데이터 타입 비교 1. CHAR vs VARCHAR

MySQL을 사용하다 보면, 문자열 데이터 타입을 어떤 것을 사용하여 저장할지 항상 고민이 많이 됩니다. 보통은 저장되는 데이터의 길이를 정확히 제한하기 어렵기 때문에 varchar 타입을 관습적으로 많이 사용하게 되는 것 같습니다. 오늘은 이러한 습관을 좀 없애보고자 정확히 각 타입의 장단점을 비교하고 언제 char 타입을 사용하는 것이 유리한 지, 언제 varchar타입을 사용하는 것이 유리한 지 정리해보려 합니다.

char vs varchar

통상 일반적인 문자열을 저장할 때 CHAR 혹은 VARCHAR 중 하나의 타입을 선택하게 됩니다. 추후에 TEXT와 VARCHAR을 비교하는 글도 작성하겠지만, TEXT의 경우 매우 긴 문자열을 저장할 때 고민할 요소이지, 255자 정도 이내에서 사용할 데이터 타입에는 적절하지 않습니다.

공통점

1. 저장되는 문자의 길이를 표기합니다.

 

저희가 표기하는 char(N) 이나 varchar(N)에서 N은 저장할 수 있는 문자의 개수를 의미합니다. 이 N을 바이트수로 착각하시는 분이 많은데, 기본적으로 문자의 개수를 의미합니다.

 

예를 들어

abcdefghijk 는 11자이기 때문에 char(10)에 저장할 수 없습니다.

한컴타자연습 은 6자이기 때문에 char(10)에 저장할 수 있습니다.

 

mysql> SELECT LENGTH('abcdefghijk');
+------+-----------------+
|  LENGTH('abcdefghijk') |
+------+-----------------+
|                      11|
+------+-----------------+

mysql> SELECT LENGTH('한컴타자연습');
+------+-----------------+
|  LENGTH('한컴타자연습')   |
+------+-----------------+
|                      18|
+------+-----------------+

 

abcdefghijk는 11byte입니다. 한컴타자연습 은 18byte입니다.

즉 문자의 byte수는 문자열 타입에서 저장할 수 있냐 없냐를 판단할 때 의미가 없습니다.

 

차이점

1. CHAR은 항상 고정적으로 데이터를 저장할 공간을 마련해 둡니다.VARCHAR은 현재 필요한 저장공간 만큼만 할당하여 사용합니다.

 

name1이라는 컬럼이 char(10), name2가 varchar(10)으로 각각 선언되어 있다고 해보겠습니다.

또한, 이번에서는 Latin 1 charset을 사용중이라고 가정하겠습니다.

 

이 테이블에 John이라는 데이터를 저장하게 되면 다음과 같이 저장되게 됩니다.

name1 char(10)
J o h n (공백) (공백) (공백) (공백) (공백) (공백)
name2 varchar(10)
4(length byte) J o h n

 

위 예시처럼, char(10)은 무조건 10 byte를 할당하게 되고, 남는 공간은 공백으로 채워두게 됩니다. 하지만 varchar의 경우 데이터의 가변 길이 중 저장될 데이터의 길이를 표시할 length bytes를 제외하고 딱 필요한 만큼만 할당받고 저장하게 됩니다.

 

이번에는 UTF8MB4 charset을 사용중이라고 가정하겠습니다.

 

이 테이블에 멍분이 라는 데이터를 저장하게 되면 다음과 같이 저장되게 됩니다.

name1 char(10)
            (공백)
name2 varchar(10)
9
(length)
           

 

한글은 글자당 3byte를 사용하게 되므로 위와 같이 저장되게 될 것입니다.

여기서 조금 다른 점이 있다면, UTF8MB4는 한 글자가 4byte까지 할당될 수 있는 문자셋입니다. 즉 위 Latin1 문자셋과 다르게 만약 char(10) 타입이라면 최대 40byte까지를 미리 공간에 할당해두어야 하는 것 아닌가?라는 질문을 할 수 있습니다.

 

MySQL 공식문서를 보면 이 부분에 대한 설명이 있는데요, 요약하자면

 

  • 고정 길이 문자 집합의 내부 저장 형식:
    • CHAR(10)과 같은 고정 길이 문자 열은 고정 길이 형식으로 저장됩니다. 예를 들어, CHAR(10)은 항상 10문자의 공간을 차지합니다. 이는 문자 집합이 고정 길이일 때 적용됩니다.
  • CHAR(N) 최소 바이트 예약:
    • CHAR(N)을 위해 최소한 N 바이트가 예약됩니다. 이는 많은 경우 열 업데이트를 인덱스 페이지 단편화 없이 수행할 수 있도록 합니다. 

 

즉 고정 길이 문자열(latin 1)일 경우 N이 곧 예약되는 byte 수를 의미하고, 그렇지 않을 경우 최소 N바이트가 예약된 후, 글자수를 바이트로 계산하였을 때 필요한 만큼 공간을 할당한다는 것입니다.

 

char(10)의 경우에는 최대 10자가 예약 가능하기 때문에 '멍분이'를 저장하게 되면 10byte를 예약 후 9byte를 저장공간으로 사용하게 되고, '멍분이바보'를 저장하게 되면 딱 15byte만 저장공간으로 사용하게 된다는 의미입니다.

 

name1 char(10)
                   

(추가로 할당하는 공간 없음)

 

2. 최대 저장할 수 있는 문자열의 개수가 다릅니다.

char 타입은 255자까지 허용하고, varchar 타입은 16383까지 허용합니다.

 

3. varchar 타입은 별도로 저장된 데이터의 길이를 관리하는 값이 있습니다.

 

이 길이 바이트 (length-bytes)는

 

0 ~ 255 bytes까지는 1 바이트가 필요하고,

255 ~ 16383 bytes까지는 2 바이트가 필요합니다. 

 

1byte 8bit입니다. 따라서 00000000 ~ 11111111까지를 1바이트로 표현 가능하기 255 bytes까지만 1byte로 표현이 가능합니다.

 

물론 앞서 설명드린 것 처럼 가변길이 문자셋의 경우 char 타입도 varchar 타입과 유사하게 글자수(N) 초과 바이트에 대해서는 필요한 만큼만 저장공간을 사용하게 되기 때문에 저장 공간 길이 관리가 필요합니다. 따라서 이 경우는 char 타입도 length bytes가 필요하게 됩니다.

 

언제 어느 타입을 선택해야 할까요?

일반적으로 고정 길이를 가지고 있으면 char 타입을, 가변 길이를 가지고 있으면 varchar 타입을 선택해야 한다는 의견이 있습니다.

 

이 기준대로 예시를 한번 들어보면.

모든 한국인의 주민등록 번호는 13자리이기 때문에 위 기준대로 주민등록 번호를 저장하려면 char(13)타입을 선택해야 합니다.

하지만 한국에서는 대부분 가변길이 문자셋(UTF8MB4)를 사용할 것이기 때문에 char(13)을 사용하더라도 14byte를 사용하여 저장하겠죠. varchar(13)을 사용하더라도 마찬가지로 14byte를 사용할 것입니다. 따라서 이 경우 어떤 타입을 선택해도 차이가 거의 없습니다. 

 

이렇게 보면 거의 대부분의 경우 varchar를 사용하는 것이 좋아 보이는데,

char 타입의 장점이 부각되는 경우가 있습니다.

 

MySQL에서는 빈번한 업데이트로 인한 단편화가 발생할 수 있습니다.

 

  • MySQL은 데이터를 페이지라는 고정 크기의 블록에 저장합니다. 일반적으로 InnoDB 스토리지 엔진에서는 페이지 크기가 16KB입니다. 하나의 테이블은 여러 페이지로 구성되며, 각 페이지에는 여러 행이 저장됩니다.
  • varchar 타입의 경우 필요한 만큼의 공간만 할당하고 ,남은 공간은 다른 데이터를 저장하는 것에 사용하게 됩니다.
  • 특정 행의 VARCHAR 열에 저장된 데이터를 더 긴 데이터로 업데이트하면, 원래의 페이지에 그 데이터를 모두 담을 공간이 부족할 수 있습니다.
  • 이 경우 InnoDB는 데이터를 저장할 수 있는 충분한 공간을 찾기 위해 데이터를 다른 페이지로 옮길 수 있으며, 이로 인해 원래 페이지에 빈 공간이 생기면서 단편화가 발생합니다.

아래 그림을 보면 이해가 빠르게 되실 거에요.

 

varchar(10) 컬럼에 john이라는 데이터를 입력했다고 가정해 보겠습니다.

이 데이터를 abcd라는 문자열로 업데이트하게 되면 저장공간을 그대로 사용하고 내부 데이터만 inplace 형태로 수정될 것입니다.

UPDATE my_table SET my_column = 'abcd' WHERE my_column = 'john';

 

즉 같은 길이 혹은 더 작은 길이로 업데이트하면 데이터의 위치가 변경되지 않고 저장되게 되는데요, 만약 이 컬럼을 chalrie로 업데이트하게 되면 어떻게 될까요?

UPDATE my_table SET my_column = 'charlie' WHERE my_column = 'john';

 

MySQL은 할당해 놓은 저장공간에 데이터를 업데이트할 수 없기 때문에 데이터를 저장할 새로운 공간을 찾게 됩니다. 이렇게 되면 기존 데이터가 저장되어 있던 공간에는 삭제 flag를 통해 삭제된 데이터라는 것이 표시되고, 데이터를 저장할 수 있는 새로운 공간에 저장하게 됩니다. 이렇게 저장공간을 compact하게 사용하지 못하고, 데이터의 위치가 계속 벌어지는 현상을 데이터 조각화 현상이라 하는데요,

 

컬럼을 변경할 때마다 데이터 페이지 내부에 조각화 현상이 계속 심해지고 이를 조정하기 위해서는 page reorganization(optimization) 작업을 지속적으로 해주어야 합니다. 조각화 현상이 심해지면 당연히 성능은 계속 하락하고, 특히 인덱스 키 컬럼에 대해 조각화 현상이 발생하면 전체적으로 조회 성능에 크게 악영향을 미치게 됩니다.

 

위 현상을 고려했을 때 값의 가변길이 폭이 좁고, 빈번하게 업데이트가 발생하는 경우 char타입이 varchar타입보다 더 유리합니다.