MySQL COUNT 최적화: 다양한 COUNT 문법의 성능 분석

MySQL에서 레코드 수를 집계할 때 사용하는 COUNT 함수는 여러 형태로 작성할 수 있습니다. COUNT(*), COUNT(1), COUNT(pk_column), COUNT(column) 등의 차이점과 성능 특성을 InnoDB 스토리지 엔진 관점에서 분석합니다.

COUNT 함수의 동작 원리

COUNT()는 집계 함수로, 지정한 표현식이 NULL이 아닌 레코드의 수를 반환합니다. 이 원리를 이해하면 각 문법의 차이를 명확히 파악할 수 있습니다.

COUNT(식별자 컬럼) 실행 메커니즘

예시 쿼리:

SELECT COUNT(user_no) FROM orders;

MySQL 서버 레이어는 카운터 변수를 유지하며 InnoDB에 레코드를 순차 요청합니다. InnoDB가 반환한 각 레코드의 user_no 값을 검증하여 NULL이 아니면 카운터를 증가시킵니다.

InnoDB는 B+ 트리 구조로 데이터를 관리합니다. 클러스터드 인덱스는 실제 데이터를 리프 노드에 저장하고, 세컨더리 인덱스는 리프 노드에 기본키 값만 저장합니다.

테이블에 세컨더리 인덱스가 존재하면 옵티마이저는 클러스터드 인덱스보다 작은 세컨더리 인덱스를 선택합니다. 동일 레코드 수에서 세컨더리 인덱스가 차지하는 공간이 더 작아 I/O 비용이 감소하기 때문입니다.

COUNT(1) 실행 메커니즘

예시 쿼리:

SELECT COUNT(1) FROM orders;

상수 값 1은 결코 NULL이 될 수 없습니다. InnoDB가 레코드를 반환하면 서버 레이어는 컬럼 값을 읽지 않고 즉시 카운터를 증가시킵니다.

COUNT(식별자 컬럼)과 비교하면 레코드에서 실제 컬럼 값을 추출하는 단계가 생략되어 약간의 오버헤드가 줄어듭니다.

COUNT(*) 실행 메커니즘

많은 개발자가 SELECT *에서의 *와 동일하게 모든 컬럼을 읽는다고 오해합니다. 하지만 COUNT(*)에서 *는 특별하게 처리됩니다.

MySQL은 COUNT(*)를 COUNT(0)으로 내부 변환합니다. 따라서 COUNT(*)와 COUNT(1)은 동일한 실행 계획을 사용하며 성능 차이가 없습니다.

MySQL 5.7 공식 문서:

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

여러 세컨더리 인덱스가 존재할 때 옵티마이저는 key_len이 가장 작은 인덱스를 선택합니다. 세컨더리 인덱스가 없는 경우에만 기본키 인덱스를 사용합니다.

COUNT(일반 컬럼) 실행 메커니즘

예시 쿼리:

-- status는 일반 컬럼, 인덱스 없음
SELECT COUNT(status) FROM orders;

해당 컬럼에 인덱스가 없으면 풀 테이블 스캔이 발생합니다. NULL 여부를 확인해야 하므로 레코드의 실제 컬럼 값을 읽어야 합니다.

또한 세컨더리 인덱스 최적화가 적용되지 않아 가장 높은 I/O 비용이 발생합니다.

성능 비교 요약

구문인덱스 활용컬럼 값 읽기성능 등급
COUNT(*)세컨더리 인덱스 우선불필요최상
COUNT(1)세컨더리 인덱스 우선불필요최상
COUNT(식별자)세컨더리 인덱스 우선필요
COUNT(일반 컬럼)인덱스 없으면 풀 스캔필수

COUNT(*)나 COUNT(1) 사용 시 세컨더리 인덱스를 생성하면 옵티마이저가 자동으로 가장 효율적인 인덱스를 선택합니다.

COUNT가 순회 방식을 사용하는 이유

MyISAM은 테이블 메타정보에 row_count를 유지하여 O(1)로 즉시 반환합니다. 하지만 InnoDB는 MVCC로 인해 동일 시점의 여러 트랜잭션이 서로 다른 레코드 수를 봐야 할 수 있습니다.

예시 상황:

-- 세션 A
BEGIN;
SELECT COUNT(*) FROM orders;  -- 100만건 반환

-- 세션 B (동시에)
BEGIN;
INSERT INTO orders VALUES (...);
COMMIT;

-- 세션 A에서 다시
SELECT COUNT(*) FROM orders;  -- 여전히 100만건 (일관된 뷰)

WHERE 조건이 있는 경우 MyISAM도 InnoDB와 같이 테이블 스캔이 필요합니다.

대용량 테이블 COUNT 최적화 전략

천만 건 이상 테이블에서 COUNT(*) 실행 시 수 초 이상 소요될 수 있습니다.

방법 1: 근사값 활용

EXPLAIN SELECT * FROM orders;
-- rows 필드의 추정값 활용

SHOW TABLE STATUS LIKE 'orders';
-- Rows 필드의 근사값 활용

검색 결과 개수나 대시보드 대략 수치에 적합합니다.

방법 2: 별도 카운터 테이블

CREATE TABLE order_stats (
    stat_name VARCHAR(50) PRIMARY KEY,
    total_count BIGINT UNSIGNED NOT NULL
);

-- 트리거 또는 애플리케이션 레벨에서 동기화
INSERT INTO orders (...) VALUES (...);
UPDATE order_stats SET total_count = total_count + 1 WHERE stat_name = 'total_orders';

정확한 실시간 집계가 필요한 경우 트랜잭션 내에서 카운터 테이블을 함께 갱신합니다.

태그: MySQL InnoDB Query Optimization Index Aggregation Functions

6월 17일 18:56에 게시됨