MySQL8.0で全文検索(mecab)を試してみる。


この記事はディップ Advent Calendar 2019の19日目の記事です。

まえがき

DockerでMySQL8.0の全文検索機能をお試しする環境をつくってみます。
そもそも全文検索って何なのさ、というのは以下の記事がとても参考になります。
検索エンジンはいかにして動くのか?

なお、コンテナ作成後のSQLは、ホストからMySQL Workbenchで接続・実行しています。
※dockerコンテナの中からmysql clientで接続して、最後の全文検索用SQLを投げようとしたところ、日本語を入力した途端に文字が消えるという事象に遭遇し、断念。。

構築手順

フォルダ構成
.
├── docker   
│   └── mysql8.0-mecab
│       ├── Dockerfile
│       ├── conf.d
│       │   └── my.cnf
│       └── initdb.d
│           └── init.sql
├── docker-compose.yml

上のようなフォルダ構成で、下記のファイルを用意していきます。

docker-compose.yml

my.cnfはローカルからコンテナの/etc/mysql/conf.dにマウントさせてます。
また、/docker-entrypoint-initdb.dにマウントされたシェルスクリプトやSQLが初回起動時に実行されます。
今回はmecabプラグインのインストールや検証用テーブル作成用のSQLを配置しました(後述)。

docker-compose.yml
version: '3.3'
services:
  mysql80-mecab:
    build: ./docker/mysql8.0-mecab
    container_name: mysql8.0-mecab
    environment:
      MYSQL_DATABASE: sample_db
      MYSQL_USER: jabe
      MYSQL_PASSWORD: jabe123
      MYSQL_ROOT_PASSWORD: jabe123
      TZ: 'Asia/Tokyo'
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql8.0-mecab/conf.d:/etc/mysql/conf.d
      - ./docker/mysql8.0-mecab/initdb.d:/docker-entrypoint-initdb.d

Dockerfile

mecabのインストールをします。また、echo "dicdir=/var/lib/mecab/dic/ipadic-utf8" > /etc/mecabrc の部分でmecabの単語辞書をIPA辞書に切り替えています。

Dockerfile
FROM mysql:8.0
RUN apt-get update && apt-get install -y        \
    mecab                                       \
    libmecab-dev                                \
    mecab-ipadic-utf8                           \
    locales                                     \
&&  locale-gen ja_JP.UTF-8                      \
&&  echo "dicdir=/var/lib/mecab/dic/ipadic-utf8" > /etc/mecabrc
ENV LANG        ja_JP.UTF-8
ENV LC_CTYPE    ja_JP.UTF-8

my.cnf

MySQLにmecabrc(mecabの設定ファイル。前述のDockerfileで単語辞書のパスを記載しました。)のパスを通します。
また、mecabがトークン解析をする際のトークンの分割単位を決めます。

my.cnf
[mysql]
default-character-set=utf8mb4
[client]
default-character-set=utf8mb4
[mysqld]
# mecabrcファイルの格納先
loose-mecab-rc-file=/etc/mecabrc
# mecabが解析時にトークン分割する際のトークンサイズを指定
innodb_ft_min_token_size=1
# デフォルト認証プラグインの設定
default-authentication-plugin = mysql_native_password

collation-server=utf8mb4_unicode_ci
character-set-server=utf8mb4
skip-character-set-client-handshake

初期化用SQL

前述のコンテナ初回起動時に実行させるSQLです。ここでは以下をやっています。

  1. mecabプラグインのインストール
  2. テーブル作成&初期データ投入

特に、fulltext (street_address) with parser mecab で全文検索用のindexを作成します。

init.sql
use sample_db;
/*** 1. mecabプラグインのインストール ***/
install plugin mecab soname 'libpluginmecab.so';

/*** 2. テーブル初期化 ***/
drop table if exists employee;
create table employee
(
        id                          int unsigned auto_increment not null primary key
    ,   name                        varchar(40)
    ,   street_address              varchar(90)
    ,   fulltext (street_address)   with parser mecab
) engine=innodb character set utf8mb4;

insert into employee (id, name, street_address) values (1, "太郎","東京都新宿区");
insert into employee (id, name, street_address) values (2, "次郎","京都府京都市北区");
insert into employee (id, name, street_address) values (3, "三郎","愛媛県松山市");
insert into employee (id, name, street_address) values (4, "四郎","香川県高松市");
insert into employee (id, name, street_address) values (5, "五郎","徳島県徳島市");
insert into employee (id, name, street_address) values (6, "花子","高知県高知市");

コンテナ起動&確認

docker-composeコマンドでコンテナの起動、確認します。

# 起動
$ docker-compose up -d
# 起動確認
$ docker-compose ps 
# ログチェック
$ docker-compose logs

DB確認

コンテナ起動後、MySQLにログインし、以下が確認できればOKです!

  1. mecabプラグインがあること。
  2. テーブル作成&初期データ登録ができてること。
MySQLの状態確認。
Execute:
> show create table employee

+ ---------- + ----------------- +
| Table      | Create Table      |
+ ---------- + ----------------- +
| employee   | CREATE TABLE `employee` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(40) DEFAULT NULL,
  `street_address` varchar(90) DEFAULT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `street_address` (`street_address`) /*!50100 WITH PARSER `mecab` */ 
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+ ---------- + ----------------- +
1 rows

Execute:
> select * from employee

+ ------- + --------- + ------------------- +
| id      | name      | street_address      |
+ ------- + --------- + ------------------- +
| 1       | 太郎    | 東京都新宿区            |
| 2       | 次郎    | 京都府京都市北区         |
| 3       | 三郎    | 愛媛県松山市            |
| 4       | 四郎    | 香川県高松市            |
| 5       | 五郎    | 徳島県徳島市            |
| 6       | 花子    | 高知県高知市            |
+ ------- + --------- + ------------------- +
7 rows

いざ、全文検索。

「京都」を検索して「東京都」がでなければ、できあがり。

Execute:
> select * from employee
where match (street_address)
against ('京都' in natural language mode)

+ ------- + --------- + ------------------- +
| id      | name      | street_address      |
+ ------- + --------- + ------------------- +
| 2       | 次郎    | 京都府京都市北区        |
+ ------- + --------- + ------------------- +
2 rows

あとがき

最後までお付き合い頂きありがとうございました。
不備、おかしなところあれば、ご指摘頂けますと幸いです。
また、まえがきで遭遇した事象について有力情報お待ちしておりますm(_ _)m

参考記事

以下を参考にさせていただきました。感謝です。
https://dev.mysql.com/doc/refman/8.0/en/fulltext-search-mecab.html
https://qiita.com/clustfe/items/3f5160ce77c5db9c7b30
https://qiita.com/ucan-lab/items/b094dbfc12ac1cbee8cb
https://qiita.com/motoki_giants/items/0c3b9d174edef9410310
https://qiita.com/furu8ma/items/75e5b1df29fef04ec7f1