Java Enum 타입 데이터베이스 저장 형식은 뭐가 좋을까? (enum, varchar, tinyint)
Java Enum Type 데이터 DB 저장 형식 (enum, varchar, tinyint)
해당 내용은 데이터베이스 설계 과정에서 Java Enum 타입의 데이터는 어떤 형식으로 저장되는 것이 좋은가에 대해 고민하며 정리한 내용입니다.
실제로는 'varchar'와 'tinyint' 사이에서 고민했으나, mysql의 경우에는 'enum' 타입도 존재하기 때문에 mysql enum 타입에 대해서도 함께 비교하였습니다.
enum을 함께 비교하기는 하였지만 mysql의 enum 타입은 여러 이유로 인해 많이 쓰이지는 않는데요.
아래 내용에서는 mysql enum 타입의 특징과 쓰이지 않는 이유에 대해 간단하게 살펴보고 이어서 enum, varchar, tinyint 각각의 타입에 대해 실제 데이터를 넣고 조회하는 과정을 통해 성능 비교도 해보았습니다.
mysql enum 타입의 특징과 단점
구글링을 통해 'mysql enum 단점'을 검색하면 관련 내용들을 찾을 수 있는데요. 그중에서 몇 가지 내용을 요약하면 아래와 같습니다.
1. 다른 DBMS로 데이터 마이그레이션 불가
enum은 데이터베이스 표준이 아니며 mysql의 확장 기능입니다. 때문에 다른 데이터베이스로 데이터 마이그레이션이 필요한 경우에 문제가 생길 수 있습니다.
2. 데이터 정규화에 위반되는 설계
enum으로 사용할 값(ex. bronze, silver, gold)에 대한 데이터는 실제 데이터가 저장되어야 할 곳(테이블)이 아닌 다른 곳(테이블 칼럼 정의 등)에 남게 되는데요. 이는 데이터 정규화에 위반되는 설계입니다.
(해당 내용은 enum 뿐만 아니라 아래에서 살펴볼 tinyint에서도 동일하게 적용되는 것으로 데이터 정규화에 위반되는 설계에 해당되는데요. 참조 테이블을 만들어 해당 테이블에 enum으로 사용할 값을 넣고 해당 데이터의 fk 값을 연결하는 것이 원칙적으로는 맞는 방법입니다.)
3. enum 칼럼에 정의된 값 재사용 불가
enum 칼럼에 정의된 데이터는 다른 테이블에서 재사용할 수 없다는 문제점이 있는데요.
(2번 문제에 대한 방안과 같이 만약 참조 테이블이 있다면, 해당 참조 테이블을 통해 enum 값을 재사용할 수 있게 됩니다.)
똑같은 enum 칼럼을 다른 테이블에 동일하게 적용할 수는 있지만, 이 방법을 사용할 경우 enum에 대해 정의된 값이 바뀌었을 때 데이터의 동일성을 유지하기 위해 지속적으로 맞춰주어야 한다는 단점이 있습니다.
이 외에 mysql enum 타입의 특징으로는 enum 데이터 값에 대해 index가 1부터 저장된다는 것이 있으며, 때문에 숫자를 enum 타입에 사용했을 때 헷갈릴 수 있다는 점이 있습니다.
(0은 유효하지 않은 값의 index로 사용되는데요. bronze, silver, gold가 정의된 enum 필드에 platinum 또는 diamond 등, 정의되지 않은 데이터를 넣게 되면 내부적으로 0이라는 index로 저장됩니다.)
enum, varchar, tinyint 성능 비교
우선 3가지 타입의 데이터 크기에 대해 먼저 살펴보면, tinyint와 enum의 경우 1byte를 사용합니다.
(사용되는 데이터 값의 옵션은 255개 이하라고 가정)
varchar의 경우 가변의 문자열 타입으로 저장되는 문자 charset에 따라 저장되는 크기가 다르며,
(utf8은 최대 3byte까지 지원하며, 이모지의 경우 4byte를 사용하기 때문에 utf8mb4를 사용합니다.)
데이터를 삽입할 때 데이터 값 외에 삽입된 문자열의 길이를 함께 저장한다는 특징이 있습니다.
(255 글자 이하에는 1byte, 255 글자가 넘는 문자열의 경우 2byte의 추가 공간을 필요로 합니다.)
즉, 단순 데이터 크기를 비교했을 때는 varchar가 tinyint와 enum에 비해 훨씬 많은 공간을 차지하는데요.
데이터가 저장되는 공간뿐만 아니라 인덱스 저장소의 크기도 더 많이 차지한다는 것을 생각해 보며 아래 테스트 내용을 살펴보겠습니다.
#enum
CREATE TABLE user_enum (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
grade ENUM ('bronze', 'silver', 'gold') DEFAULT 'bronze',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY (grade)
) ENGINE = InnoDB COLLATE utf8mb4_bin;
#varchar
CREATE TABLE user_varchar (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
grade VARCHAR(10) DEFAULT 'bronze',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY (grade)
) ENGINE = InnoDB COLLATE utf8mb4_bin;
#tinyint
CREATE TABLE user_int (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
grade TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY (grade)
) ENGINE = InnoDB COLLATE utf8mb4_bin;
(user_enum, user_varchar, user_int 테이블)
성능 비교를 위해 위 3개 테이블을 만들었으며, 각 테이블의 grade 칼럼의 타입을 enum, varchar, tinyint로 주었습니다.
성능 비교를 위해 mysql procedure를 통해 각 테이블 당 50만 건의 데이터를 넣었으며 bronze, silver, gold에 대한 데이터는 약 16만 6천 건 정도로 고르게 분포되어 있습니다.
먼저 user_enum 테이블을 grade 값으로 조회한 결과입니다. 평균 지속 시간(duration)은 0.573969입니다.
이어서 user_varchar 테이블을 grade 값으로 조회한 결과입니다. 평균 지속 시간(duration)은 0.677006으로 user_enum 테이블을 조회했을 때에 비해 조금 더 오래 걸린 것을 확인할 수 있습니다.
마지막으로 user_int 테이블을 grade 값으로 조회한 결과입니다. 평균 지속 시간(duration)은 0.560922로 enum과 비슷하지만 조금 더 빠른 결과가 나왔습니다.
단편적인 결과일 수 있지만, 해당 테스트로 'tinyint < enum << varchar'라는 결과를 얻을 수 있었는데요.
enum 칼럼의 경우 위에서 본 것처럼 여러 단점으로 인해 많이 사용되지 않기 때문에 결국 성능적인 측면에서는 tinyint를 사용하는 것이 좋다는 결론을 얻을 수 있었습니다.
(java에서는 AttributeConverter 인터페이스를 구현한 CustomConverter를 통해 enum의 legacyCode 값을 db에 편하게 저장할 수 있는데요. 해당 방법에 대해서는 바로 아래 관련 포스팅이 있으니 참고하시면 좋을 것 같습니다.)
< java enum legacy code db 저장 방법 >
2022.10.01 - [Programming/Java] - Java Enum 활용하기1 - AttributeConverter
< 참고 자료 >
https://hackernoon.com/what-is-the-difference-between-enum-varchar-and-int-data-types-in-mysql
https://eyecandyzero.tistory.com/259