MySQL FULLTEXT 인덱스 — 전문 검색 구현과 한계

InnoDB FULLTEXT 인덱스의 역인덱스 구조, NATURAL LANGUAGE·BOOLEAN·QUERY EXPANSION 검색 모드, 최소 토큰 길이와 불용어 설정, 한국어 검색을 위한 MeCab 파서까지 다룹니다.

· 7 min read · PALDYN Team

지난 글에서 표현식 기반 Functional Index를 살펴봤습니다. 이번 글에서는 MySQL InnoDB의 FULLTEXT 인덱스로 전문(Full-Text) 검색을 구현하는 방법과 한계를 다룹니다.

LIKE vs FULLTEXT

LIKE '%keyword%' 검색은 인덱스를 사용하지 못하고 Full Table Scan을 수행합니다. 텍스트 칼럼이 크거나 행 수가 많으면 성능이 급격히 저하됩니다. FULLTEXT 인덱스는 이 문제를 해결하기 위해 역인덱스(Inverted Index)를 사용합니다.

역인덱스는 “어떤 단어가 어느 문서에 있는가”를 저장하는 구조입니다. 검색어를 찾을 때 모든 문서를 스캔하는 대신, 역인덱스에서 해당 단어의 문서 목록을 바로 조회합니다.

InnoDB FULLTEXT 인덱스 구조

FULLTEXT 인덱스 생성

-- 테이블 생성 시
CREATE TABLE articles (
    id      INT          NOT NULL AUTO_INCREMENT PRIMARY KEY,
    title   VARCHAR(200) NOT NULL,
    body    TEXT         NOT NULL,
    FULLTEXT INDEX ft_title_body (title, body)
) ENGINE = InnoDB;

-- 기존 테이블에 추가
ALTER TABLE articles ADD FULLTEXT INDEX ft_body (body);

-- 또는
CREATE FULLTEXT INDEX ft_title ON articles (title);

하나의 FULLTEXT 인덱스에 여러 칼럼을 포함할 수 있습니다. MATCH() 함수에서 동일한 칼럼 조합을 사용해야 합니다.

세 가지 검색 모드

BOOLEAN MODE 연산자 예시

IN NATURAL LANGUAGE MODE (기본값)는 입력 텍스트를 자연어로 처리합니다. 관련도(relevance) 점수를 계산해 반환하므로 ORDER BY score로 정렬하면 가장 관련성 높은 결과가 앞에 옵니다. 전체 행의 50% 이상에 등장하는 단어는 너무 흔하다고 판단해 자동으로 무시합니다.

IN BOOLEAN MODE는 연산자를 사용한 정밀한 검색을 지원합니다. +는 필수, -는 제외, *는 와일드카드, ""는 구문 검색입니다. 50% 임계값이 없어 소수 문서에서도 정확한 필터링이 가능합니다.

WITH QUERY EXPANSION은 1차 결과에서 관련 단어를 추출해 쿼리를 자동으로 확장한 뒤 2차 검색합니다. 관련 주제의 문서도 포함되는 ‘맹목적 확장(Blind Query Expansion)‘이라고도 합니다.

-- 관련도 점수 활용
SELECT
  id,
  title,
  MATCH(title, body) AGAINST('database performance') AS relevance
FROM articles
WHERE MATCH(title, body) AGAINST('database performance')
ORDER BY relevance DESC
LIMIT 10;

-- Boolean Mode: +필수 -제외
SELECT * FROM articles
WHERE MATCH(title, body)
  AGAINST('+InnoDB +index -deprecated' IN BOOLEAN MODE);

-- 구문 검색 ("exact phrase")
SELECT * FROM articles
WHERE MATCH(body) AGAINST('"clustered index"' IN BOOLEAN MODE);

최소 토큰 길이와 불용어

-- 최소 토큰 길이 확인 (기본 3)
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';
-- → 3  (3자 미만 단어는 인덱싱 안 됨)

-- 최소 길이 변경 (my.cnf)
-- innodb_ft_min_token_size = 2

-- 불용어 확인
SELECT * FROM information_schema.innodb_ft_default_stopword;

-- 커스텀 불용어 테이블 사용
CREATE TABLE my_stopwords (value VARCHAR(30));
INSERT INTO my_stopwords VALUES ('the'), ('a'), ('이'), ('의');
SET GLOBAL innodb_ft_server_stopword_table = 'mydb/my_stopwords';

-- 변경 후 인덱스 재빌드 필요
ALTER TABLE articles DROP INDEX ft_title_body;
ALTER TABLE articles ADD FULLTEXT INDEX ft_title_body (title, body);

한국어 전문 검색 — MeCab 파서

기본 파서는 공백과 구두점을 기준으로 토큰을 분리합니다. 한국어, 일본어, 중국어처럼 공백 없이 단어가 붙어 있는 언어에서는 형태소 분석이 필요합니다.

MySQL은 일본어/한국어용 MeCab 파서 플러그인을 지원합니다.

-- MeCab 파서 플러그인 설치 (OS 수준에서 mecab 설치 후)
INSTALL PLUGIN mecab SONAME 'libpluginmecab.so';

-- MeCab 파서로 FULLTEXT 인덱스
CREATE TABLE posts (
    id      INT  NOT NULL AUTO_INCREMENT PRIMARY KEY,
    content TEXT NOT NULL,
    FULLTEXT INDEX ft_content (content) WITH PARSER mecab
) ENGINE = InnoDB;

-- 또는 ngram 파서 (내장, MySQL 5.7.6+)
-- 연속 N글자를 토큰으로 분리 (기본 n=2)
CREATE FULLTEXT INDEX ft_ngram ON posts (content) WITH PARSER ngram;
SET GLOBAL ngram_token_size = 2;  -- 2-gram: 모든 2글자 조합을 인덱싱

ngram 파서는 외부 의존성 없이 한국어/중국어/일본어를 처리합니다. 모든 연속 N글자 조합을 인덱싱하므로 인덱스 크기가 크지만, 별도 형태소 분석기 없이 부분 일치 검색이 가능합니다.

FULLTEXT의 한계와 대안

한계설명
전체 단어 매칭 기본부분 일치는 word* 와일드카드 필요
관련도 알고리즘 단순TF-IDF 기반, Elasticsearch 대비 정교함 부족
한국어 형태소 분석MeCab 또는 ngram 파서 별도 설정 필요
실시간 업데이트 지연삽입 직후 보조 테이블 캐시에서 메인으로 병합 지연
분산 검색 불가단일 인스턴스 한정

대규모 전문 검색이 필요하다면 MySQL FULLTEXT 대신 Elasticsearch, OpenSearch, Typesense 같은 전문 검색 엔진과 동기화하는 방법을 고려하세요. MySQL FULLTEXT는 중소 규모의 단일 서버 환경에서 간단하게 검색 기능을 추가할 때 유용합니다.

MySQL 인덱스 이야기는 여기까지입니다. B+ Tree 기본 구조부터 클러스터드/세컨더리 인덱스, Leftmost Prefix, ICP, Invisible Index, Functional Index, FULLTEXT까지 InnoDB 인덱스의 핵심을 체계적으로 살펴봤습니다.


지난 글: MySQL Functional Index — 표현식 기반 인덱스로 함수 쿼리 최적화


읽어주셔서 감사합니다. 😊
#mysql#fulltext-index#full-text-search#inverted-index#boolean-mode#mecab#전문검색