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 — 표현식 기반 인덱스로 함수 쿼리 최적화


읽어주셔서 감사합니다. 😊