Category Archives: Mysql

MySQL FULLTEXT 검색

MySQL은 % 연산자를 사용하여 패턴연산을 하는 대신, 단어 또는 구문에 대한 검색을 지원하며 FULLTEXT 검색이라고 부른다. MySQL은 3가지 종류의 FULLTEXT 검색 방식을 지원한다.

FULLTEXT 검색 방식

  • 자연어 검색
    검색 문자열을 단어 단위로 분리한 후, 해당 단어 중 하나라도 포함되는 행을 찾는다.
  • 불린 모드 검색
    검색 문자열을 단어 단위로 분리한 후, 해당 단어가 포함되는 행을 찾는 규칙을 추가적으로 사용할 수 있다.
  • 쿼리 확장 검색
    2단계에 걸쳐서 검색을 수행한다. 첫 단계에서는 자연어 검색을 수행한 후, 첫 번쨰 검색의 결과에 매칭된 행을 기반으로 검색 문자열을 재구성하여 두 번째 검색을 수행한다. 이는 1단계 검색에서 사용한 단어와 연관성이 있는 단어가 1단계 검색에 매칭된 결과에 나타난다는 가정을 전제로 한다.

이 글에서는 자연어 검색과 불린 모드 검색만을 다루고자 한다. 그리고 영어 데이터가 아닌 한글 데이터를 기준으로 한다.

FULLTEXT 검색을 위한 주의사항

FULLTEXT 검색을 지원하기 위해서는 아래의 사항들을 주의해야 한다.

FULLTEXT 인덱스 생성

FULLTEXT 검색을 하려는 컬럼에 대해 FULLTEXT 인덱스를 생성해야 한다. FULLTEXT 인덱스는 MyISAM 엔진을 사용하는 테이블에 대해서만 생성할 수 있다. 또한 컬럼 데이터 타입은 반드시 Text, Binary Char, Varchar 타입을 가져야 한다. 또한 한글 데이터를 검색하려면 테이블 인코딩을 utf8을 사용해야 한다.

FULL TEXT 인덱스는 테이블을 생성할 때 생성하거나, 또는 이미 만들어진 테이블에 대해서도 테이블을 생성할 수 있다. 예를 들어 게시판 검색 기능을 만든다고 해보자. 게시글을 저장할 posts 테이블을 만들고, 여기에 게시글의 제목(gtitle), 본문(gdesc) 컬럼을 추가한다고 하자. 만약 제목만을 FULLTEXT 검색을 하고자 한다면,

와 같이 테이블을 생성한다.

만약 이미 만들어진 테이블에 대해 FULLTEXT 인덱스를 생성해야 한다면,

와 같이 생성한다.

FULLTEXT 인덱스는 하나 이상의 컬럼에 대해서 생성할 수 있다. 만약 제목 검색뿐만 아니라, 본문 검색까지 지원하려면 아래와 같이 게시글 제목과 본문에 대해서도 FULLTEXT 인덱스를 생성해야 한다.

FULLTEXT 검색 엔진 설정하기

검색어가 너무 짧은 경우 아무런 검색결과도 나오지 않는다. 이때 짧다는 기준은 4글자 이하다. 만약 2글자의 검색어를 지원하려면 최소 검색어 길이 값을 2로 수정해야 한다. my.cnf 설정파일을 열어서 ft_min_word_len 변수 값을 기본값인 4에서 2로 변경한다.

만약 FULLTEXT 인덱스를 생성한 경우라면 인덱스를 삭제한 후 재생성해야 한다. 또는 아래와 같이 인덱스를 갱신할 수 있다.

이제 FULLTEXT 검색 본론으로 넘어가 보자.

자연어 검색

게시물의 제목과 본문에 “로고”라는 단어가 포함된 게시글을 검색하는 경우를 생각해 보자. FULLTEXT 검색은 아래와 같이 수행한다.

검색 결과는 다음과 같다.

mysql_fulltext_natural_language

무언가를 해낸듯한 기분이 들겠지만 약간의 문제가 있다. 이 부분은 조금 후에 불린 검색에서 다루기로 하고, 검색의 정확도를 수치로 보여주는 방법을 살펴보자.

 

mysql_fulltext_natural_language_show_score

보는 바와 같이 검색의 정확도(score)에 따라 내림차순 정렬되어 결과가 표시됨을 알 수 있다. 물론 1단어뿐만 아니라 여러 단어로도 검색이 가능하다.

 

mysql_fulltext_natural_language_multi_words

자연어 검색은 구문을 단어 단위로 분할한 후, 해당 단어 중 1개라도 포함된 행을 검색한다. 따라서 6번째 결과와 같이 “예쁜 팔찌 만들어”와 같이 관련성이 낮은 게시글도 검색되게 된다.

불린 모드 검색

이제 불린 모드 검색을 이용하여 자연어 검색에서 부딪혔던 문제를 완화해 보자. 불린 검색이 자연어 검색와 크게 차이나는 점은

  • 검색의 정확도에 따라 결과가 정렬되지 않는다.
  • 구문 검색이 가능하다
  • 필수(+), 예외(-), 부분(*) 연산자를 사용할 수 있다

등이다.
앞서 “귀여운 로고”를 단어 검색이 아닌 구문 검색, 즉 “귀여운 로고”가 그대로 포함된 게시글을 검색하고자 한다면,

구문을 쌍따옴표(” “)로 묶은 후, 불린 모드(boolean mode)로 검색을 실행한다.

mysql_fulltext_boolean_mode

만약 맨 마지막의 여행영상 게시글은 검색에서 제외하려면 예외 연산자(-)를 사용할 수 있다.

이제 부분 연산자(*)를 살펴보자. 앞서 자연어 검색 첫 번째 예제에서 “로고”를 검색한 결과를 보면, 모두 “로고” 단어가 독립적으로 존재한다. 다시 말해 “로고를”, “로고와” 등과 같은 단어는 검색되지 않는다. 이처럼 한글에서 형태소를 잘라야만 제대로 인덱스가 구성되지만 쉬운일이 아니다. 따라서 검색할 때 “로고로 시작하는 모든 단어”와 같은 패턴을 사용할 수 있다. 명확한 예를 위해 게시글 제목(gtitle)에 대해서만 FULLTEXT 검색을 실행해 보자.

먼저 자연어 검색은 다음과 같다.

위와 같이 검색하면

mysql_fulltext_natural_language_2

와 같이 “로고를”이 포함된 행이 매칭된다. 하지만 자연어 검색에서 아래와 같이 ‘로고’를 사용하면 해당 행이 검색되지 않는다.

그러나 불린 검색을 이용하여 부분 검색 연산자를 사용하면 검색이 가능하다.

메타 검색 기능 UI 구성

이제 FULLTEXT 검색 기능을 사용하여 서비스에 메타 검색 기능을 추가한다면 아래와 같이 UI를 구성해볼 수 있다.
일단 기본적으로 모든 검색은 불린 검색을 사용한다. 그리고 사용자가 입력한 단어는 구문 검색(” “)이 아닌 경우 공백을 기준으로 분리한 후, * 연산자를 추가한다. 아래는 입력 예다.

mysql_fulltext_search_ui_prototype

  • 1단어를 입력한 경우 –> [로고*] 로 검색을 수행한다.
  • 2단어를 입력한 경우 –> [귀여운* 로고*] 로 검색을 수행한다.
  • 구문 검색을 입력한 경우 –> [“귀여운 로고”*] 로 검색을 수행한다.
  • 예외 연산자를 사용한 경우 –> [“귀여운 로고”* -영상] 으로 검색을 수행한다.

References

[번역] MySQL 5.1의 InnoDB에서 MySQL 테이블 최적화하기

원문 : http://www.laurencegellert.com/2011/07/mysql-maintenance-tasks-for-innodb-with-mysql-5-1/

때때로 MySQL 5.1 데이터베이스는 몇가지 관리 작업을 수행해야 한다. 내 경우를 예로 들면, 운영서버에서 겨우 400k 행을 가진 두 개의 테이블을 조인하는 간단한 쿼리문을 실행하는데 너무 오래 걸리는 경우가 있었다. 실제로 이 쿼리문이 실행되는데 약 30초에서 100초 가량이 걸렸다. 테스트와 검수작업 후에 58ms로 처리 시간을 줄였다. 쿼리문의 컬럼은 이미 인덱스가 생성되어 있었기 때문이다. 이 정도 처리속도라면 다행히도 사용자들이 불편해 하는 정도는 아니였지만, 나는 이정도 처리 속도도 괜히 성가시게 느껴졌다. 해결책은 간단했는데, 몇가지 명령문을 실행해서 정리 작업을 하면 되었기 때문이다. 이 정리작업을 하고 나니, 동일한 쿼리문을 운영서버에서 실행하는데 겨우 4.8ms 밖에 걸리지 않게 되었다. 정말이지 만족스러운 결과였다.

내가 한 일들

  1. Backup – 데이터베이스 백업하기
  2. Check – 데이터베이스 체크하기
  3. Optimize – 데이터베이스 최적화하기
  4. Analyze – 데이터베이스 분석하기

각 수행 단계들에 대한 상세 설명

1. 먼저 mysqldump 명령문을 이용해서 데이터베이스를 백업한다.

프로시져 또는 함수가 있다면 반드시 -routines 인자를 사용해야 한다. 또한 트리거를 사용한다면 반드시 -triggers 인자를 사용해야 한다.

여기에 더해서 다른 시스템에서 백업한 데이터베이스를 실제로 재생성하여 백업이 정확한지 확인해야 한다.

만약 데이터베이스가 너무 커다면 이처럼 데이터베이스 전체를 백업하는 일은 사실 불가능하다. 하지만 데이터베이스가 너무 크다면, 이미 복제 서버를 구축해서 백업 시스템을 활용중일 것이다.

2 Check

테이블 무결성을 검사한다.

http://dev.mysql.com/doc/refman/5.1/en/check-table.html

단일 테이블에 대해 검사를 하려면:

콘솔에서 데이터베이스의 전체 테이블을 검사하려면 :

테이블 무결성 검사는 정기적으로 하는 것이 좋다.

3. Optimize

단편화 제거 작업(defrag operation)과 같이, optimize table 명령문을 사용하면 사용하지 않은 공간을 회수할 수 있다. MyISAM 엔진에서는 optimize 명령문은 말그대로 단편화 제거 작업만을 수행한다. 반면 InnoDB 엔진의 경우 내부적으로 ALTER TABLE문을 실행하여, MySQL 서버에 대해 테이블과 인덱스를 재생성하도록 요청한다.

http://dev.mysql.com/doc/refman/5.1/en/optimize-table.html

단일 테이블에 대해 optimize를 하려면:

콘솔에서 데이터베이스의 전체 테이블을 optimize를 하려면:

만약 InnoDB라면 결과에 “Table does not support optimize, doing recreate + analyze instead” 메시지가 나온다.

4. Analyze

analyze를 실행하면 인덱스를 재생성하여 성능을 최적화하는데, 키를 재분배하기 때문이다. 만약 인덱스가 제대로 생성되어 있음에도 slow query가 발생한다면, analyze를 실행하는 것을 고려해 볼만하다. analyze를 실행하면 read 락이 걸린다. 만약 모든 테이블이 InnoDB 엔진을 사용한다면, optimize 과정에 이미 analyze가 포함되어 있다.

http://dev.mysql.com/doc/refman/5.1/en/analyze-table.html

단일 테이블에 대해 analyze를 하려면:

콘솔에서 데이터베이스의 전체 테이블을 analyze를 하려면:

InnoDB에서 analyze를 하는 경우, 몇가지 특이점이 있다. 특히 analyzer가 취하는 샘플의 갯수가 다를 수 있다는 점이다(샘플의 갯수는 innodb_stats_sample_pages 옵션으로 설정할 수 있다). 기본값이 매우 작기 때문에, analyze를 여러번 시도하게 될 때, 그때마다 결과가 달라질 수도 있다.

더 자세한 내용은 아래를 참조하라.

http://dev.mysql.com/doc/refman/5.1/en/innodb-restrictions.html