오늘의 주제는 “PK 성능 테스트” 입니다.
MySQL 의 경우 Oracle / MSSQL 과 달리 클러스터드 인덱스를
사용하면서 내부적으로 논리적인 키값을 사용하지 않고,
테이블의 Primary Key
값을 통해 구성되는 것이 특징입니다.
그렇기 때문에 MySQL 을 사용할 경우 PK의 대상이 되는 데이터 타입이 중요합니다.
대부분의 엔지니어분들은 "INT" 혹은 "VARCHAR" 타입을 사용하여 키를 설정하는데요,
과거에는 CHAR / VARCHAR , 어떤 데이터 타입이 더 효율적인지가 화제였다고 합니다.
(물론 테스트 결과, 동일했다 라는 결과가 있었습니다.)
이처럼 해당 글에서는 어떤 데이터 타입으로 기본 키(Primary Key)를 설정하면 좋을지 테스트를 해보려고 합니다.
1. 운영 환경
Aurora MySQL 3.03 (MySQL 8.0.26)
2. 비교 대상
bigint auto_increment
varchar utf8mb4_unicode_ci
varchar latin1_general_ci
3. 테이블 구조
원본 테이블 구조
테스트 테이블 구조
xxx_bigint
xxx_utf8mb4
xxx_latin1
INDEX FULL SCAN
(첫번째 Leaf 노드부터 마지막 Leaf 노드까지 전체 스캔 방식)TABLE FULL SCAN
(테이블의 첫번째 레코드부터 마지막 레코드까지 전체 스캔 방식)INDEX RANGE SCAN
(필요한 범위만 탐색하는 인덱스 스캔 방식)PK RANDOM ACCESS
(물리적으로 떨어진 페이지에 랜덤 접근 방식)테스트 수행 방법에 맞춰 테스트를 진행할 경우,
데이터가 캐시에 저장되어 테스트 목적과 다른 결과가 나올 수 있으므로
회차가 변경될 때마다 인스턴스 재부팅을 통해 캐시 초기화
MySQL 의 경우 Oracle / MSSQL 과 달리 실행계획을 정확히 알 수 없다는 것이 특징입니다.
그렇기 때문에 정확한 측정이 불가능하여 프로파일을 통한 excute 시간만을 체크했습니다.
해당 결과값만으로는 부정확한 테스트가 될 확률이 높지만, 어느 정도 납득할 수 있는 결과가 나올 것으로 추정됩니다.
-- TEST DATA INSERT (1개 테이블마다 인스턴스 재부팅)
INSERT INTO test_database.xxx_bigint (`a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`)
SELECT `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`
FROM test_database.origin_table
ORDER BY 1 DESC
LIMIT 1000000
; -- 1000000 / 3m 12.45s (EC2) || excuting: 192.369735
-- 1000000 / 3m 9.91s (EC2) || excuting: 189.869955
-- 1000000 / 2m 51.99s (EC2) || excuting: 171.936766
INSERT INTO test_database.xxx_utf8mb4 (`a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`)
SELECT `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`
FROM test_database.origin_table
ORDER BY 1 DESC
LIMIT 1000000
; -- 1000000 / 3m 33.78s (EC2) || excuting: 213.660593
-- 1000000 / 3m 29.52s (EC2) || excuting: 209.431003
-- 1000000 / 3m 18s (EC2) || excuting: 198.220575
INSERT INTO test_database.xxx_latin1 (`a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`)
SELECT `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`
FROM test_database.origin_table
ORDER BY 1 DESC
LIMIT 1000000
; -- 1000000 / 3m 24.36s (EC2) || excuting: 204.248438
-- 1000000 / 3m 26.48s (EC2) || excuting: 206.371021
-- 1000000 / 2m 58.86s (EC2) || excuting: 178.775363
SELECT *
FROM test_database.xxx_bigint
WHERE group_code = 'g20240101'
AND user_number LIKE '%010%'
LIMIT 10000 ;
-- 10000 건 조회
SELECT *
FROM test_database.xxx_utf8mb4
WHERE group_code = 'g20240101'
AND user_number LIKE '%010%'
LIMIT 10000 ;
-- 10000 건 조회
SELECT *
FROM test_database.xxx_latin1
WHERE group_code = 'g20240101'
AND user_number LIKE '%010%'
LIMIT 10000 ;
-- 10000 건 조회
-- xxx_bigint (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| 1 | SIMPLE | xxx_bigint | NULL | ref | user_id,user_id_2,user_id_3,user_id_4 | user_id_4 | 88 | const | 67218 | 11.11 | Using where |
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
-- xxx_utf8mb4 (1,2,3회차 모두 동일한 결과)
+----+-------------+--------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| 1 | SIMPLE | xxx_utf8mb4 | NULL | ref | user_id,user_id_2,user_id_3,user_id_4 | user_id_2 | 88 | const | 71398 | 11.11 | Using where |
+----+-------------+--------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
-- xxx_latin1 (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
| 1 | SIMPLE | xxx_latin1 | NULL | ref | user_id,user_id_2,user_id_3,user_id_4 | user_id_2 | 88 | const | 71398 | 11.11 | Using where |
+----+-------------+-------------------+------------+------+-----------------------------------------------+-------------+---------+-------+-------+----------+-------------+
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.33s | 0.32s | 0.32s |
xxx_utf8mb4 | 0.46s | 0.43s | 0.42s |
xxx_latin1 | 0.40s | 0.37s | 0.37s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.32s | 0.32s | 0.31s |
xxx_utf8mb4 | 0.45s | 0.41s | 0.44s |
xxx_latin1 | 0.39s | 0.38s | 0.37s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.33s | 0.30s | 0.31s |
xxx_utf8mb4 | 0.44s | 0.43s | 0.41s |
xxx_latin1 | 0.36s | 0.37s | 0.37s |
SELECT user_address, count(*)
FROM test_database.xxx_bigint
GROUP BY user_address
ORDER BY count(*) desc
LIMIT 100 ;
-- 100 건 조회
SELECT user_address, count(*)
FROM test_database.xxx_utf8mb4
GROUP BY user_address
ORDER BY count(*) desc
LIMIT 100 ;
-- 100 건 조회
SELECT user_address, count(*)
FROM test_database.xxx_latin1
GROUP BY user_address
ORDER BY count(*) desc
LIMIT 100 ;
-- 100 건 조회
-- xxx_bigint (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| 1 | SIMPLE | xxx_bigint | NULL | ALL | NULL | NULL | NULL | NULL | 757645 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
-- xxx_utf8mb4 (1,2,3회차 모두 동일한 결과)
+----+-------------+--------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| 1 | SIMPLE | xxx_utf8mb4 | NULL | ALL | NULL | NULL | NULL | NULL | 899563 | 100.00 | Using temporary; Using filesort |
+----+-------------+--------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
-- xxx_latin1 (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| 1 | SIMPLE | xxx_latin1 | NULL | ALL | NULL | NULL | NULL | NULL | 945490 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 2.25s | 2.22s | 2.20s |
xxx_utf8mb4 | 2.24s | 2.24s | 2.24s |
xxx_latin1 | 2.21s | 2.19s | 2.17s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 2.28s | 2.24s | 2.27s |
xxx_utf8mb4 | 2.26s | 2.24s | 2.21s |
xxx_latin1 | 2.26s | 2.22s | 2.23s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 2.30s | 2.27s | 2.28s |
xxx_utf8mb4 | 2.33s | 2.33s | 2.29s |
xxx_latin1 | 2.31s | 2.27s | 2.27s |
SELECT category, SUM(price), count(*)
FROM test_database.xxx_bigint FORCE KEY(user_id)
WHERE group_code LIKE 'g202401%'
GROUP BY category ;
SELECT category, SUM(price), count(*)
FROM test_database.xxx_utf8mb4 FORCE KEY(user_id)
WHERE group_code LIKE '202401%'
GROUP BY category ;
SELECT category, SUM(price), count(*)
FROM test_database.xxx_latin1 FORCE KEY(user_id)
WHERE group_code LIKE '202401%'
GROUP BY category ;
-- xxx_bigint (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+-------+---------------+---------+---------+------+--------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+-------+---------------+---------+---------+------+--------+----------+----------------------------------------+
| 1 | SIMPLE | xxx_bigint | NULL | range | user_id | user_id | 90 | NULL | 229760 | 100.00 | Using index condition; Using temporary |
+----+-------------+-------------------+------------+-------+---------------+---------+---------+------+--------+----------+----------------------------------------+
-- xxx_utf8mb4 (1,2,3회차 모두 동일한 결과)
+----+-------------+--------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
| 1 | SIMPLE | xxx_utf8mb4 | NULL | range | PRIMARY,user_id,user_id_2 | PRIMARY | 90 | NULL | 219272 | 100.00 | Using where; Using temporary |
+----+-------------+--------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
-- xxx_latin1 (1,2,3회차 모두 동일한 결과)
+----+-------------+-------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
| 1 | SIMPLE | xxx_latin1 | NULL | range | PRIMARY,user_id,user_id_2 | PRIMARY | 24 | NULL | 219272 | 100.00 | Using where; Using temporary |
+----+-------------+-------------------+------------+-------+-----------------------------------+---------+---------+------+--------+----------+------------------------------+
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.48s | 0.45s | 0.45s |
xxx_utf8mb4 | 0.24s | 0.22s | 0.22s |
xxx_latin1 | 0.21s | 0.20s | 0.20s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.48s | 0.46s | 0.46s |
xxx_utf8mb4 | 0.24s | 0.22s | 0.22s |
xxx_latin1 | 0.22s | 0.20s | 0.20s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.48s | 0.46s | 0.45s |
xxx_utf8mb4 | 0.23s | 0.22s | 0.22s |
xxx_latin1 | 0.21s | 0.20s | 0.19s |
SELECT count(*)
FROM (
SELECT b.user_id, b.user_name, b.group_code
FROM test_database.xxx_middle_bigint a
JOIN test_database.xxx_bigint b ON a.middle_user_id = b.user_id
) a ;
-- 100,000 건 조회
SELECT count(*)
FROM (
SELECT b.user_id, b.user_name, b.group_code
FROM test_database.xxx_middle_utf8mb4 a
JOIN test_database.xxx_utf8mb4 b ON a.middle_user_id = b.user_id
) a ;
-- 100,000 건 조회
SELECT count(*)
FROM (
SELECT b.user_id, b.user_name, b.group_code
FROM test_database.xxx_middle_latin1 a
JOIN test_database.xxx_latin1 b ON a.middle_user_id = b.user_id
) a ;
-- 100,000 건 조회
-- xxx_bigint (회차별 차이 발생)
+----+-------------+-------+------------+--------+----------------+----------------+---------+--------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+----------------+----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 94932 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+----------------+----------------+---------+---------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 101574 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 93636 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
-- xxx_utf8mb4 (회차별 차이 발생)
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 101007 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 94932 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 98091 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
-- xxx_latin1 (회차별 차이 발생)
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 99711 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 99387 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | middle_user_id | middle_user_id | 90 | NULL | 94041 | 100.00 | Using index |
| 1 | SIMPLE | b | NULL | eq_ref | user_id | user_id | 90 | test_database.a.middle_user_id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+-----------------+---------+--------------------------------+--------+----------+-------------+
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.51s | 0.49s | 0.49s |
xxx_utf8mb4 | 0.58s | 0.56s | 0.55s |
xxx_latin1 | 0.35s | 0.32s | 0.32s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.51s | 0.51s | 0.50s |
xxx_utf8mb4 | 0.58s | 0.54s | 0.55s |
xxx_latin1 | 0.33s | 0.32s | 0.32s |
테이블명 | 조회결과_1 | 조회결과_2 | 조회결과_3 |
---|---|---|---|
xxx_bigint | 0.52s | 0.50s | 0.50s |
xxx_utf8mb4 | 0.58s | 0.54s | 0.56s |
xxx_latin1 | 0.35s | 0.31s | 0.32s |
SCAN 방식 | TABLE_NAME |
---|---|
INDEX FULL SCAN | xxx_bigint |
TABLE FULL SCAN | xxx_latin1 |
INDEX RANGE SCAN | xxx_latin1 |
PK RANDOM ACCESS | xxx_latin1 |
varchar latin1_general_ci
방식이 제일 우수한 성능을 보였다varchar latin1_general_ci
설정이 도움이 될 수 있을 것 같다.많은 기술 블로그를 보면 “정수형이 좋다” 또는 “비즈니스에 따라서 다르게 설계해야 한다” 라는 내용이 가장 많았으나 실제 스트레스 테스트를 통한 지표는 많지 않아 의문이 많았습니다.
이번 테스트를 통해서 스캔 방식에 따른 성능 차이가 발생한다는 것을 알 수 있었으며 이를 통해 추후 비즈니스 설계 과정에서 해당 내용을 참고하면 좋을 것 같습니다.