COUNT(*) & COUNT(DISTINCT) 튜닝

1. 들어가며

실무에서 COUNT 쿼리를 사용할 일은 생각보다 자주 있습니다. 다만 COUNT 쿼리가 가볍다는 일반적인 인식이 있어서, 잘못된 쿼리로 예상치 못한 성능 이슈를 겪는 경우도 있습니다. 오늘은 COUNT 쿼리의 잘못된 인식을 짚고, 튜닝하는 방법에 대해 정리하고자 합니다.

 

 

2. COUNT 쿼리에 대한 잘못된 인식

일반적으로 COUNT(*) 쿼리는 빠를 것으로 기대합니다.

그럼 다음의 두 쿼리는 어떤 것이 빠를까요?

SELECT COUNT(*)
WHERE ix_fd = 'A' AND non_ix_fd = 'B';

SELECT *
WHERE ix_fd = 'A' AND non_ix_fd = 'B';

 

위 쿼리는 인덱스 사용 가능하지만 커버링 인덱스 실행 조건은 불가능합니다.

따라서 ix_fd의 index로 대상 레코드를 찾은 뒤에 non_ix_fd가 B인지 확인하여 동작합니다.

 

정답: 인덱스를 참조하기 때문에 COUNT에 비해 SELECT가 빠를 이유는 없습니다. 

다만 네트워크 사용량은 COUNT가 더 빠를 수는 있습니다.

 

또한 일반적으로

  • SELECT *는 LIMIT과 함께 사용되지만
  • SELECT COUNT(*)의 경우 LIMIT 없이 사용됩니다.

→ SELECT *는 10건만 읽는데

SELECT COUNT(*)는 백만건 읽어야 하면

후자가 당연히 오래 걸릴 것입니다.

 

3. 성능 개선 방법

 

당연하겠지만 위처럼 모든 쿼리를 covering index로 튜닝하면 빠르게 될 수 있습니다.

하지만 모든 쿼리가 covering index가 될리가.. 없겠죠..?

예를 들어 non-covering index의 두 번째 쿼리도 WHERE 절은 모두 index에 명시되어 있지만 count 절에서 non-index를 접근하기 때문에 not null인지 확인해야하여 non-covering-index로도 불가능합니다.

 

또한 ORM 라이브러리에서 의도치 않게 COUNT(DISTINCT)로 동작하는 경우가 있습니다.

중복 제거를 위해 임시 테이블을 만들어 저장하게 되는데, 여기에 그냥 insert를 하는게 아니라 중복된 값이 있는 경우 select를 하고 아니면 insert 하는 방식입니다.

그래서 레코드 건 별로 select와 insert를 내부적으로 일어나게 됩니다.

→ 성능적으로 2~3배 이상 느려집니다. 심지어 임시 테이블이 너무 커지면 해당 임시 테이블이 disk로 옮겨지면서 너무 느려지는 사이드 이펙트까지 발생할 수 있습니다.

 

 

COUNT(*) 튜닝

커버링 인덱스가 최선이지만, 안된다면 어떻게 해야 할까요?

  • 최고의 튜닝은 쿼리 자체를 제거하는 것입니다.
    • 전체 결과 건수 확인 쿼리 제거
    • 페이지 번호 없이, '이전' '이후' 페이지 이동
  • 쿼리를 제거할 수 없다면, 대략적 건수 활용
    • 부분 레코드 건수 조회
      • 표시할 페이지 번호만큼의 레코드 건수만 확인
    • 임의의 페이지 번호는 표기
      • 첫 페이지에서 10개 페이지 표시 후, 실제 해당 페이지로 이동하면서 페이지 번호 보정
    • 통계 정보 활용

통계 정보 활용을 조금 더 구체적으로 이야기 해보겠습니다.

SELECT TABLE_ROWS as rows
FROM INFORMATION_SCHEMA.tables
WHERE schema_name = ? AND table_name = ?

 

INFORMATION_SCHEMA.tables를 보면 전체 column 예측치를 조회해서 사용할 수 있습니다.

조건문이 있다면 where 조건문이 index 사용하도록 하고, 위처럼 쿼리 조건이 있는 경우 실행 계획을 활용해볼 수 있습니다.

물론 정확도가 낮으므로 페이지 이동하면서 보정이 필요해집니다.

 

 

'Backend > DB' 카테고리의 다른 글

Lateral Derived Table  (0) 2024.12.26
Stored Function  (0) 2024.12.26
CHAR vs VARCHAR vs TEXT  (2) 2024.12.26
트랜잭션과 락  (2) 2024.03.07
MySQL 아키텍쳐  (0) 2024.03.06