Skip to content
isdnetworks
Go back

OR로 묶으면 인덱스를 못 탄다

목록을 뽑는 조회가 느렸는데 걸려 있는 조건 자체는 단순해 보였다. 두 컬럼을 OR로 묶어 어느 한쪽이 맞으면 되는 조건이었고 두 컬럼 모두 인덱스가 걸려 있었다.

Table of contents

Open Table of contents

단순해 보이던 조건

각각에 인덱스가 있으니 어느 쪽이든 타야 할 것으로 봤다. 그런데 실제로는 인덱스를 안 쓰고 전체를 훑고 있었다.

인덱스가 있는데 안 탄다면 그 조건이 인덱스를 쓸 수 없는 형태이거나 옵티마이저가 안 쓰는 편이 싸다고 본 것이다. 그래서 EXPLAIN을 떠서 어느 쪽인지부터 갈라야 했다.

type에 찍힌 것은 전체 스캔이었고 두 인덱스 중 어느 것도 안 쓰고 있었다. 한쪽 인덱스로 좁혀도 나머지 조건에 해당하는 행이 그 밖에 있으니 한 번의 범위 스캔으로는 안 된다는 것까지는 맞다.

다만 그것이 전체를 훑는 것 말고 방법이 없다는 뜻은 아니다. key1 = 1 OR key2 = 2 형태는 index_merge의 union 알고리즘이 걸리는 표준 사례이고 그때는 typeindex_merge가 찍힌다. 두 인덱스를 각각 훑어 결과를 합치는 것을 엔진이 알아서 한다는 말이다. 우리 조회에서 그것이 안 나온 것은 원리상 불가여서가 아니다. 조건이 그 형태에서 벗어났거나 옵티마이저가 그 경로를 안 고른 것이다.

나누면 각자 인덱스를 탄다

그래서 이 형태를 UNION으로 묶은 두 조회로 나눴다. 각 조회는 조건이 하나씩이므로 자기 인덱스를 그대로 탄다.

엔진이 안 골라 준 index_merge를 손으로 쓴 셈이다. EXPLAIN을 다시 뽑으니 두 조회 모두 인덱스를 타고 있었고 시간이 크게 줄었다. 다만 UNION은 중복을 지우고 UNION ALL은 그대로 두므로 어느 쪽이 맞는지를 정해야 했다.

정렬과 페이지 처리

여기서 새로 걸린 것이 결과를 정렬하는 부분이었다. 나눈 뒤에 각각을 정렬하면 합쳤을 때 전체 순서가 안 맞는다.

그래서 각 조회에 ORDER BYLIMIT을 따로 걸어 필요한 개수만큼씩 가져와 합친 다음 바깥에서 다시 ORDER BY를 걸어 필요한 만큼을 취하게 했다. 뒤 페이지로 넘어갈 때도 안쪽 LIMITOFFSET만큼 키워서 같은 방식을 그대로 써야 했다. 나누는 것이 조회는 빠르게 만들지만 뒤처리를 하나 늘린다.

그 조건이 정말 필요한지 묻는다

이 작업을 하는 중에 한 번 되물어본 것이 있다. 두 컬럼 중 어느 하나라도 맞으면 되는 조건이 왜 필요한지였다.

확인해 보니 두 값 중 하나만 채워지는 구조였고 어느 쪽이 채워졌는지를 담는 컬럼을 하나 두면 조건이 하나로 줄어든다. 그것이 근본적인 해결이었지만 스키마를 바꿔야 하는 일이라 급한 것과 나눠서 처리했다. 바꾼 뒤에는 결과가 원래와 같은지와 경계 조건과 자료가 많은 계정에서의 동작을 각각 확인했다.

정리


Share this post on:

Previous Post
남이 그 컬럼을 읽고 있으면 그것이 약속이었다
Next Post
채널마다 처리기가 따로 여덟 개였다