본문 바로가기

Backend/Database

MySQL의 문자열 데이터 타입 비교 2. VARCHAR vs TEXT

저번 글에 이어 MySQL의 VARCHAR 타입과 TEXT 타입을 정리해 보도록 하겠습니다.

 

일반적으로 RDBMS에서 TEXT나 BLOB과 같은 대용량 데이터를 지정하는 컬럼 타입을 LOB 타입이라 합니다. 저번 글에서 CHAR 타입과 VARCHAR 타입에 대해 이야기 했었는데요. 문자열 타입 중에 매우 긴 타입을 저장할 때는 CHAR 타입을 사용할 수 없습니다. (255자까지밖에 저장할 수 없음). 따라서 VARCHAR 타입과 TEXT 타입 중 선택을 해야 합니다.

 

VARCHAR 타입은 만능일까?

VARCHAR 타입을 CHAR타입과 비교하여 설명하는 많은 글을 읽어보면, 가장 강조하는 부분이 데이터를 저장할 때 필요한 공간만큼만 할당하여 사용한다입니다.

 

이렇게만 읽으면 varchar 타입은 항상 필요한 만큼만 할당해서 사용하기 때문에 varchar(30)과 같이 작은 글자 수 제한을 걸어서 사용하는 것보다 varchar(255)를 사용하는 것이 항상 유리한 것이 아닌가?라는 질문을 해볼 수 있습니다. 실제로 많은 오픈소스 라이브러리나 개발자들은 관습적으로 varchar(255)를 사용하기도 합니다. 

 

하지만 그러지 말아야 할 이유가 몇 가지 있습니다.

 

1. MySQL은 최대 row size 제한이 있습니다. (공식 문서)

The internal representation of a MySQL table has a maximum row size limit of 65,535 bytes, even if the storage engine is capable of supporting larger rows.

 

즉 최대 65535 bytes 까지만 제한한다 인데요, 문자열 제한이 긴 varchar타입을 여러 개 사용하다 보면 금방 이 제한에 도달할 수 있습니다.

 

create table t1(a varchar(8000), b varchar(8000), c varchar(400));

ERROR 1118 (42000): Row size too large. 
The maximum row size for the used table type, not counting BLOBs, is 65535. 
This includes storage overhead, check the manual. 
You have to change some columns to TEXT or BLOBs

 

현재 varchar로 총 16400 자를 예약한 예제입니다. 이 경우 utf8mb4 charset을 사용 중이므로 16400 * 4 = 65600 bytes가 필요하므로 위 row size 제한인 65535 bytes를 넘어가기 때문에 error가 발생합니다.

 

create table t1(a varchar(8000), b varchar(8000), c text(400));

Query OK, 0 rows affected (0.01 sec)

 

반면 text 타입으로 선언하게 되면 정상적으로 테이블이 생성됩니다. 

BLOB and TEXT columns only contribute 9 to 12 bytes toward the row size limit because their contents are stored separately from the rest of the row.

 

 

text 타입의 경우 data가 별도로 저장되기 때문에 전체 row size에 큰 영향을 미치지 않기 때문입니다.

즉 varchar를 습관적으로 길게 정의하게 되면, 다른 컬럼이 사용할 수 있는 최대 길이 제한에 영향이 있습니다.

 

2. VARCHAR 타입은 데이터를 읽을 때 버퍼를 할당하여 읽습니다.

이게 어떤 의미냐 하면, MySQL에서 data를 읽을 때 항상 메모리를 통해 데이터를 주고받게 되는데요, 이때 필요한 만큼 메모리를 할당하여 사용하고, 사용이 완료되면 메모리를 해제하는 작업을 반복하게 됩니다.

 

이때 중요한 점은 메모리를 할당할 때에 저장된 데이터의 크기가 아닌 선언된 타입의 최대 크기만큼 이 할당됩니다.

즉 varchar(255)를 사용 중이라면, 실제 저장된 데이터가 'John'과 같은 4byte 데이터라 하더라도 MySQL은 항상 255 * 4 byte를 할당하여 데이터를 주고받습니다.

 

아래 예시처럼 John, Korea, Seoul과 같은 단어들이 저장되어 있을 경우에도 항상 3060 Bytes를 할당하여 읽고 client에게 반환을 하게 되는 것이죠.

 

긴 문자열을 저장할 컬럼에는 TEXT를 사용하는 것이 맞을까?

여기까지 읽게 되면, 긴 문자열을 저장할 때 varchar 타입이 갖는 단점들이 보여 text 타입을 사용하여 저장하는 것이 유리해 보입니다.

하지만 text 타입은 다음과 같은 문제가 있습니다.

 

1. TEXT 타입은 메모리 버퍼를 사용할 수 없습니다.

위 그림에서 메모리 버퍼에 필요한 만큼 메모리를 할당 후 데이터를 교환한 뒤, 메모리를 해제하는 방식으로 동작한다 하였는데요, 이 메모리 버퍼는 여러 커넥션이 공유합니다.

 

 

즉 여러 커넥션이 같은 요청을 할지라도, 버퍼는 한 번만 할당되어 이 버퍼를 공유하여 사용할 수 있는 캐싱기능이 내장되어 있습니다.

 

하지만 만약 c가 text타입이라면 여러 커넥션에 대해 매번 text 데이터의 실제 공간만큼 새로 할당하고, 사용해야 합니다.

 

이러한 이유 때문에 text column을 조회할 때는 항상 조심해야 합니다.

 

VARCHAR vs TEXT 타입 선택 기준

VARCHAR

  • 최대 길이가 (상대적으로) 크지 않은 경우
    • 상대적으로 크다면, row data size 제한에 쉽게 도달할 수 있음.
  • 테이블 데이터를 읽을 때 항상 해당 컬럼이 필요한 경우
    • 메모리 버퍼 재활용 가능하기 때문.
  • DBMS 서버의 메모리가 (상대적으로) 충분한 경우
    • 메모리 버퍼를 할당하여 db에서 캐싱하고, 사용하기 때문.

TEXT

  • 최대 길이가 (상대적으로) 큰 경우
  • 테이블에 길이가 긴 문자열 타입 컬럼이 많이 필요한 경우
  • 테이블 데이터를 읽을 때 해당 컬럼이 자주 필요치 않은 경우

로 요약해 볼 수 있을 것 같습니다.

 

그 외에 긴 문자 데이터를 다룰 때 주의할 부분

그 외에 긴 문자 데이터를 다룰 때 주의할 부분이 있는데요, 바로 Off-Page라는 개념입니다.

MySQL은 하나의 레코드의 사이즈가 전체 페이지의 사이즈의 절반 이상이 되면, 레코드에서 외부에 저장될 컬럼을 선정 후 외부 페이지에 저장하게 됩니다. 예를 들면 16KB 페이지에서는 8,117 바이트를 초과하는 레코드에 대해 길이가 긴 컬럼을 외부 페이지로 저장하게 되는 것이죠.

 

공식 문서를 보면 row format마다 off-page를 저장하는 저장하는 방식에 대해 서술되어 있으니 참고해 주시면 좋을 것 같습니다.

 

실제로 외부 페이지에 저장된 컬럼을 같이 조회하게 되면 성능이 매우 하락할 수 있으니, 이부분을 주의하셔서 긴 문자열 컬럼을 다루시면 좋을 것 같습니다.