인프런 커뮤니티 질문&답변

김수용님의 프로필 이미지
김수용

작성한 질문수

200억건의 데이터를 MySQL로 마이그레이션 할 때 고려했던 개념과 튜닝 방법

Multi Column Index 개념

안녕하세요, 멀티컬럼인덱스 개념 후반부 내용이 이해가 가지 않아 질문 드립니다.

해결된 질문

작성

·

283

2

멀티컬럼인덱스 개념 5:53분 경부터 설명해주신 내용이 이해가 가지 않는데요,

EXPLAIN SELECT hash_email, one_field, two_field FROM test1 WHERE two_field = 3;

여기서 idx_col1_col2 인덱스를 타서 two_field = 3인 데이터들의 primary key 목록을 가져올 수 있으니 그를 통해 실제 데이터를 조회하면 hash_email 값도 가져올 수 있는 것 아닌가요?

왜 풀스캔을 하는 건지 이해가 가지 않아 질문드립니다.

실행 계획 type의 종류를 찾아보니 이런 동작 자체가 없는 것 같기도 하고..아리송하네요

답변 2

0

김수용님의 프로필 이미지
김수용
질문자

답변 감사합니다. 늦지 않게 답변 주셔서 괜찮아요!

그동안 제가 알게된 점과 함께 조금 더 풀어서..설명을 해보겠습니다. 질문 의도가 전해지지 않았던 것 같아요

 

제가 위와 같은 실무 상황이라면 two_field의 단일 인덱스를 설정했을 거예요.

그러나, 질문드리고 싶은 점은 mysql에서 실행 계획을 정할 때

인덱스를 활용했을 때 더 효율적임에도 불구하고 풀스캔을 하는 이유가 있는지입니다.

 

제가 했던 테스트를 예로 들어보겠습니다.

위 테이블을 응용해서 컬럼 개수가 많고, row당 데이터가 큰 테이블을 생성했습니다.

CREATE TABLE test1 (
	seq INT PRIMARY KEY AUTO_INCREMENT,
	one_field BIGINT,
	two_field BIGINT,
	hash_email1 VARCHAR(700) not null default "",
    hash_email2 VARCHAR(700) not null default "",
    hash_email3 VARCHAR(700) not null default "",
    hash_email4 VARCHAR(700) not null default "",
    hash_email5 VARCHAR(700) not null default "",
    hash_email6 VARCHAR(700) not null default "",
    hash_email7 VARCHAR(700) not null default "",
    hash_email8 VARCHAR(700) not null default "",
    hash_email9 VARCHAR(700) not null default "",
    hash_email10 VARCHAR(700) not null default "",
	INDEX idx_hash_email (hash_email), -- 해시 인덱스,
	INDEX idx_col1_col2 (one_field, two_field) -- 복합 인덱스
);

 

위 테이블에 작성해주신 재귀문을 통해 약 300만 개의 데이터를 넣고, two_field = 2인 데이터를 딱 하나 삽입하였습니다.

SET @@cte_max_recursion_depth = 100000;

-- 30번 수행
INSERT INTO test1 (one_field, two_field)

WITH RECURSIVE my_cte AS (

	SELECT 1 AS n, CAST(1 AS DOUBLE) AS abc, CAST(3 AS DOUBLE) AS se

    UNION ALL

    SELECT 1+n, CAST(1+n AS DOUBLE), CAST(3+n AS DOUBLE) FROM my_cte WHERE n < 100000

)

SELECT abc, se FROM my_cte;

-- 1번 수행
INSERT INTO test1 (one_field, two_field) value (1, 2);

 

해당 테이블은 300만 + 1개의 데이터가 들어가 있고, 약 300mb의 크기를 갖게 되었습니다.

 

이 상태에서 조회문을 날려보았습니다.

-- 1. explain 결과 : type = INDEX (인덱스 풀스캔)
SELECT seq, one_field, two_field FROM test1 WHERE two_field = 2;

-- 2. explain 결과 : type = ALL (풀스캔)
SELECT * FROM test1 WHERE two_field = 2;

위 2번 쿼리는 여전히 풀스캔이 발생하였는데요,

제 생각엔 (비록 two_field가 선행이 아닐지라도) 1번 쿼리처럼 idx_col1_col2 인덱스를 타서 two_field = 2인 데이터의 seq(primary key) 값을 찾아낸 후, 그를 통해 실제 데이터를 조회하면 풀스캔에 비해 시간을 대폭 줄일 수 있을 것 같았습니다.

 

실제로 위 2번 쿼리는 제 환경에서 약 2초의 수행 시간이 걸렸는데요,

-- 3. explain 결과 : 서브쿼리 type = INDEX, 그 이후 메인쿼리 type = CONST
SELECT * FROM test1 WHERE seq = (SELECT seq FROM test6 WHERE two_field = 2);

위와 같이 명시적으로 본 질문 의도와 같이 작성한 쿼리를 날려보면 약 0.5초로 수행 시간이 많이 줄어든 것을 확인할 수 있었습니다.

 

처음 질문했던 의도는 3번 쿼리와 같이 수행하면 더 효율적임에도 불구하고 풀스캔을 하는 이유가 따로 있는지 여쭤본 것이었어요.

좀 더 찾아봐야겠지만 질문하면서 든 생각은, 이 정도 튜닝은 mysql에서 지원하는 영역이 아닌 개발자가 해야하는 영역이라는 생각이 드네요

답변 주신 것처럼 애초에 two_field를 선행으로 갖는 인덱스를 생성하면 되는 일이기도 하니까요

July님의 프로필 이미지
July
지식공유자

맞습니다. 사실 애초에 이런 부분은 인덱스를 추가 생성을 하면 해결이 되는 부분이기도 하고, 서비스 로직에서는 크게 영향이 가지는 않을 부분이기는해요.

하지만 이렇게 파고들고 하는 부분은 매우 좋은 습관이라고 생각합니다!! 질문 계속해서 감사드려요 ^_^

 

두가지 주제에 대해서 한번 말씀을 드려보도록 할게요. 추가 질문해주신 부분에서 제가 인지한 부분은 이렇게 두가지가 있습니다.

 

1. 인덱스를 활용했을 때 더 효율적임에도 불구하고 풀스캔을 하는 이유가 있는지입니다.

2. 처음 질문했던 의도는 3번 쿼리와 같이 수행하면 더 효율적임에도 불구하고 풀스캔을 하는 이유가 따로 있는지 여쭤본 것이었어요.
- SELECT * FROM test1 WHERE seq = (SELECT seq FROM test6 WHERE two_field = 2);

일단 기본적으로 인덱스를 사용하고 적용을 하는 주체는 MySQL Optimizer가 알아서 판단을 하고 최선의 쿼리 전략을 수립하게 됩니다. 그래서 기본적으로 엔진에서 알아서 처리를 해주신다고 생각을 하시면 되요.
저보다 설명을 잘해주시는거 같아서 이런 글도 찾아왔는데, 참고해 보시면 좋을 꺼 같아요.
- 블로그

 

이 옵티마이저는 개발자 측에서 특별하게 할 수 있는 부분이 없습니다. 해봤자 옵티마이저 설정 정도를 조정하는것인데, 그렇게 되면 Trade Off를 감당하실 수 없을거에요. 그래서 기본적으로 옵티마이저를 신뢰하고 서비스 개발이 진행이 되어야 합니다.

 

만약 그런데도 아무리 이해가 안갈정도로 풀스캔이 동작을 한다라고 하신다면, 서브쿼리를 한번 살펴보셔야 합니다.

기본적으로 서브쿼리는 가상의 테이블을 생성해서 전송을 한다고 보시면 됩니다.

  • MySQL 최신버전에서는 CTE 라는 항목으로 비슷하고 좀 더 가독성 있게 지원을 해주고 있습니다.

 

이는 어디까지나 가상의 테이블이기 떄문에, 실제 데이터를 저장을 하고 있지 않아요.

  • 물론 반대로 장점으로는 하나의 SQL 구문에서 여러개의 SQL을 입력 가능하다는 장점도 존재 하죠

 

실제 데이터가 담겨 있지 않다는 부분은 각각 raw나 데이터에 대한 메타 정보를 가지고 있지 못한다는 부분이고, 이로 인해서 인덱스가 정상적으로 동작을 하기가 어렵습니다.

그래서 옵티마이저가 서브 쿼리에 대해서는 인덱스를 활용 할 수가 없어요. 그러기 떄문에 큰 대용량 데이터에 대해서는 서브쿼리는 사용하지 않는 것을 추천 드립니다.

 


 

 

잠시 좀 서두가 길었는데, 다시 질문으로 돌아와서

일단 서브 쿼리를 매우 잘 활용한 케이스라고 보실 수가 있어요. 그래서 더 효율적으로 바르게 동작을 하신겁니다.

  • 마냥 단점만 있는 쿼리 형태는 아니기도하고, 클러스터형 인덱스를 사용하셨기 떄문이죠.

     

 

MySQL은 정해진 인덱스를 기반으로만 동작을 합니다.

이 인덱스를 활용해서 무언가를 튜닝하고, 쿼리 계획을 수립하지 않아요.

단지 전송해 주시는 쿼리를 기반으로 적용 가능한 인덱스가 있는지만 살펴보고 없다면, 그냥 Full Scan이 진행이 되는거에요

 

말씀해 주신 방향 처럼

제 생각엔 (비록 two_field가 선행이 아닐지라도) 1번 쿼리처럼 idx_col1_col2 인덱스를 타서 two_field = 2인 데이터의 seq(primary key) 값을 찾아낸 후, 그를 통해 실제 데이터를 조회하면 풀스캔에 비해 시간을 대폭 줄일 수 있을 것 같았습니다.

을 자동으로 MySQL에서 해주면 좋겠지만, MySQL은 단순히 DB이기 떄문에 AI처럼 추가적은 활동을 하지 않습니다. 딱 정해진 상황에서 적용 가능한 최적의 환경만을제공한다고 보시면 됩니다.

 

그래서 이러한 부분은 개발자가 스스로 서브 쿼리를 사용하든, 인덱스를 사용하든 포팅을 해주셔야 합니다.

 

신이 나서 좀 여기저기 많은 정보를 제공해 드리고 싶어서 좀 두서없이 글이 작성이 된 거 같은데.. 도움이 되실지 모르겠네요.. 다시한번 질문 남겨주셔서 감사합니다. 또 궁금한 부분이 있거나 제가 주제에 벗어난 답변을 드렸다면 귀찮으시겠지만 다시한번 질문 남겨주시면 감사하겠습니다.

김수용님의 프로필 이미지
김수용
질문자

@July님 감사합니다

제가 너무 늦게 봤군요.. ( _ _)

말씀주신대로 이런 부분은 개발자가 잘 고려해서 짜야 하는 부분인 것 같아요

옵티마이저의 설정 변경이 가능한지도 처음 알았으나, 무서우니 건들지 않도록 하겠습니다 ㅎㅎㅎ

상세히 설명해주신 덕에 의문점이 많이 풀린 것 같아요

다시 한 번 감사드립니다. 이후 진행하면서 또 모르는 점 질문 드릴게요!

0

July님의 프로필 이미지
July
지식공유자

안녕하세요. 사실 이 복합인덱스에 대해서는 실무에서 적용도 하기 꽤나 까다롭고 복잡한 부분이 많습니다. 생각보다 제약사항도 꽤나 많고요.

 

데이터를 가져오는데에는 무리가 없지만 얼마나 효율적으로 얼마나 오버헤드 없이 잘 가져 올 수 있냐의 차이로 보시면 될 꺼같아요.

 

질문자님이 말씀하신 것처럼 데이터 자체를 가져오는 것에는 문제는 없지만 풀스캔이 동작 할 수 있는 쿼리 입니다.

다음과 같은 테이블을 기준으로 설명을 드려 볼게요.

CREATE TABLE test1 (
	seq INT PRIMARY KEY AUTO_INCREMENT,
	one_field BIGINT,
    two_field BIGINT,
    hash_email VARCHAR(500),
    INDEX idx_hash_email (hash_email), -- 해시 인덱스
    INDEX idx_col1_col2 (one_field, two_field) -- 복합 인덱스
);

강의에서 다루는 테이블이며 idx_col1_col2 를 기준으로 사용이 될 것입니다.

질문자님이 주셨던 질문의 쿼리를 전송을 하게 되면, type All 로 좋은 쿼리로써 동작을 할 수는 없어요.

왜냐하면, 해당 복합 인덱스의 순서는 보장이 되어야 하기 떄문이에요.

 

그래서 다음과 같은 쿼리는 효율적으로 동작을 하게 될 거에요.

EXPLAIN SELECT hash_email, one_field, two_field FROM test1 WHERE one_field = 2 AND two_field = 3;

 

이전과의 차이는 인덱스가 지정된 필드를 모두 넣어 두었다는 차이가 있어요.
그리고 다음과 같은 쿼리도 문제가 없을 거에요.

EXPLAIN SELECT hash_email, one_field, two_field FROM test1 WHERE one_field = 2;

 

이 두 쿼리와 강의자분이 주셨던 쿼리의 차이는 one_field가 선행이 되었다는 차이가 있어요.

그러니 만약 two_field가 선행이 되어야 한다면, 그에 맞는 인덱스를 설정해보시면 어떨까 싶어요.

 

어느정도 질문에 대한 정답이 되었을까요?? 확인이 늦어서 답변을 늦게 드린거 같은데 죄송합니다 ㅠ

김수용님의 프로필 이미지
김수용

작성한 질문수

질문하기