티스토리 뷰
아래 포스팅 내용의 대부분은 당근 마켓 테크 블로그 포스팅을 참고하여 작성되었습니다.
1. 실무 사례로 살펴보는 VARCHAR와 TEXT의 차이
[ 문제의 상황 공유 ]
개발자가 다음과 같은 DDL 요청을 DBA에게 부탁한 상황이다.
alter table allowance
modify tx_id varchar(1000) not null comment '거래 내역 ID';
해당 요청을 보고 개발자와 DBA가 나눈 대화는 다음과 같다.
- DBA: tx_id가 1000자나 되나요? 1000자면 한글 기준으로 3000byte에 해당하고, 2KB가 넘는 사이즈라서요.
- 개발자: 요러요러한 비즈니스 구현 사항으로 인해 tx_id는 1000자가 될 수 있습니다.
- DBA: 그렇군요, 이해했습니다. 혹시 숫자와 문자 데이터를 분리하여 저장하긴 어려운가요? 그렇게 되면 그나마 저장 공간을 적게 사용할 수 있어서요.
- 개발자: 해싱된 값이라 분리는 어려울 것 같습니다. 혹시 VARCHAR를 TEXT로 바꾸면 효과가 있을까요?
- DBA: VARCHAR나 TEXT나 차이가 없을 것 같아요. 압축 등을 적용하는 방향으로 길이를 줄여봐도 좋을 것 같습니다.
여기서 숫자와 문자 데이터를 분리하여 저장하는 경우에 저장 공간 사용량을 줄일 수 있는 이유는 숫자 부분의 경우 컬럼을 분리하여 데이터 타입을 BIGINT(고정 크기 8바이트)로 저장하면 효율적이기 때문이다.
하지만 중요한 부분은 VARCHAR와 TEXT의 차이에 대한 부분인데, 이에 대해 살펴보도록 하자.
[ 문제의 상황 공유 ]
VARCHAR가 갖는 최대 크기의 제약
MySQL은 기본적으로 하나의 행(ROW)이 가질 수 있는 최대 행 크기를 65535 바이트로 제한하고 있다. 다음과 같은 테이블 생성 쿼리를 실행하면 실패하는 것을 확인할 수 있다.
mysql> CREATE TABLE long_varchar (id INT PRIMARY KEY, fd1 VARCHAR(1000000));
ERROR 1074 (42000): Column length too big for column 'fd1' (max = 16383); use BLOB or TEXT instead
mysql> CREATE TABLE long_varchar (id INT PRIMARY KEY, fd1 VARCHAR(16383));
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
mysql> CREATE TABLE long_varchar (id INT PRIMARY KEY, fd VARCHAR(16382));
Query OK, 0 rows affected (0.19 sec)
mysql> ALTER TABLE long_varchar ADD fd2 VARCHAR(10);
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
중간에 보면 2가지의 서로 다른 에러가 존재하는데, 이를 살펴보도록 하자.
- 하나의 행이 가질 수 있는 최대 크기는 65535바이트이라는 부분
- VARCHAR 컬럼이 가질 수 있는 최대 크기는 16383자라는 부분
VARCHAR는 가변 길이의 데이터를 저장하는 타입으로 하나의 단위가 한 문자를 나타낸다. 즉, VARCHAR(100)이라고 하는 것은 100개의 문자를 저장하겠다는 것을 의미하며, 어떠한 문자를 사용하는지에 따라서 최종적으로 사용되는 저장 공간의 크기가 달라질 수 있다. UTF-8 인코딩을 기준으로 문자의 종류 별로 다음과 같은 저장공간이 사용된다.
- 영어: 1글자 당 1바이트
- 한글: 1글자 당 3바이트
- 이모지: 1글자 당 4바이트
한 행이 가질 수 있는 최대 크기인 65535 바이트를 모두 이모지가 채웠다고 가정하면, 최대 VARCHAR(16383)를 채우고 3바이트가 남는다. 따라서 최대 ROW 크기에 맞게 VARCHAR 컬럼 역시 최대 16383자 까지 가질 수 있는 제약이 있는 것이다.
참고로 TEXT나 BLOB와 같은 LOB 타입의 컬럼은 이러한 제한 사항에 거의 영향을 미치지 않는다. 따라서 컬럼이 많은 테이블이라면 VARCHAR 타입 대신 TEXT 타입을 사용해야 할 수도 있다.
VARCHAR와 TEXT 저장 방식
MySQL에서 일반적인 레코드의 컬럼 데이터는 B-Tree(클러스터링 인덱스)로 저장되는데, 이를 B-Tree의 데이터 페이지에 컬럼 데이터가 직접 저장된다는 의미로 “Inline 저장”이라 부른다. 하지만 TEXT(또는 CLOB)나 BLOB와 같은 대용량 데이터를 저장하는 LOB 컬럼 타입의 경우에는 이를 Off-Page 저장소라는 B-Tree 외부의 공간에 저장하고, B-Tree에는 이를 참조할 수 있는 포인터만을 저장한다.
하지만 MySQL의 경우에는 LOB 타입의 컬럼을 항상 Off-Page로 저장하지는 않고, 데이터 크기가 큰 경우에만 Off-Page로 저장한다. 그리고 VARCHAR 역시 데이터의 크기가 크다면, 이를 Off-Page로 저장한다. 따라서 데이터의 저장 방식 관점에서는 둘의 차이가 다소 모호하다고 볼 수 있다.
이러한 관점에서 인덱스 설정 역시 살펴볼 수 있다. TEXT 역시 inline 저장이 가능하기 때문에 인덱스 생성이 가능한데, 대신 인덱스 생성 시에 길이를 명시해야 하는 제약이 있다.
메모리 활용 방식에서의 차이
MySQL 서버는 Handler API를 이용해서 InnoDB 스토리지 엔진과 데이터를 주고 받는데, 이때 uchar* records[2] 메모리 포인터를 이용해서 레코드 데이터를 주고 받는다.
이때 VARCHAR 타입은 최대 크기가 존재하므로 해당 크기 만큼 메모리 공간을 미리 할당받을 수 있다. 이를 통해 한 번 할당된 메모리 공간을 MySQL 서버는 내부에서 캐싱하여 여러 컨넥션에서 공유해서 사용될 수 있도록 한다.
하지만 데이터의 크기가 매우 큰 LOB 타입 컬럼의 경우에는 최대 크기만큼 할당 받으면 메모리 낭비가 너무 심할 것이다. 따라서 LOB 컬럼을 위한 메모리 공간이 records[2]에 미리 할당되어 있지 않아서 매번 레코드를 읽고 쓸 때마다 필요한 만큼 메모리를 할당 및 해제해야 한다. 참고로 이는 VARCHAR 타입이 Off-Page로 저장된 경우에도 동일하다.
즉, VARCHAR 타입의 경우 최대 공간이 존재하므로, 최대 크기로 메모리가 할당받고 MySQL 서버 내부에 캐싱하여 여러 컨넥션에서 공유해서 사용될 수 있도록 하는 반면에, LOB 타입의 경우 크기가 매우 클 수도 있고 데이터 크기도 가변적이므로 미리 메모리를 예약하면 낭비가 심하여 매번 할당 및 해제하는 것이다.
[ VARCHAR와 TEXT를 언제 사용해야 하는가? ]
당근 마켓 테크 블로그 포스팅에서는 선택 기준을 다음과 같이 제시하고 있다.
- VARCHAR
- 최대 길이가 (상대적으로) 크지 않은 경우
- 테이블 데이터를 읽을 때 항상 해당 컬럼이 필요한 경우
- DBMS 서버의 메모리가 (상대적으로) 충분한 경우
- TEXT
- 최대 길이가 (상대적으로) 큰 경우
- 테이블에 길이가 긴 문자열 타입 컬럼이 많이 필요한 경우
- 테이블 데이터를 읽을 때 해당 컬럼이 자주 필요치 않은 경우
여기서 “최대 길이가 (상대적으로) 크지 않은 경우”에 VARCHAR를 제안하는 이유는 “inline” 방식으로 데이터를 저장하여 효율적인 처리가 가능하기 때문이다. 또한 “테이블 데이터를 읽을 때 해당 컬럼이 자주 필요치 않은 경우”에는 TEXT를 권장하는데, 매번 Off-Page로부터 데이터를 읽어오며 이를 위해 메모리 공간을 할당/해제하는 것이 비효율적이기 때문이다.
참고자료
- https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html
- https://medium.com/daangn/varchar-vs-text-230a718a22a1