MySql 全文檢索兩個字符的內容沒法獲得結果

問題描述

數據庫中有以下的地址信息表,須要實現一個更具用戶輸入的任何內容進行搜索可能匹配的地址信息。html

-- MySQL版本: 5.7.25
CREATE TABLE Address 
(
    id BIGINT NOT NULL AUTO_INCREMENT,
    address VARCHAR(100) NOT NULL DEFAULT '',
    city VARCHAR(50) NOT NULL DEFAULT '',
    state VARCHAR(50) NOT NULL DEFAULT '',
    country VARCHAR(50) NOT NULL DEFAULT '',
    zip_code VARCHAR(10) NOT NULL DEFAULT '',
    FULLTEXT ftidx_location(address, city, state, country, zip_code)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

insert into Address(city, state) values ('Irving', 'TX');

容易想到利用以下的sql進行檢索。mysql

-- 這裏的 ${input} 爲用戶輸入的內容
select * from Address where match(address, city, state, country, zip_code) against (${input});

然而對於過短的輸入,如 "TX",即便數據庫中存在 state = TX 的數據,該SQL也是沒法檢索到任何結果。或者輸入 "Irvin" 也是沒法查找到內容的。下面將對該問題進行分析和解決,使用"Irvin,TX"做爲用戶輸入進行分析(不含雙引號)。sql

緣由分析

實現使用的是MySQL的FULLTEXT INDEX對(address, city, state, country, zip_code)進行了索引。FULLTEXT INDEX的配置保留了MySQL的默認配置,以下:shell

mysql> SHOW VARIABLES LIKE '%ft%';
+---------------------------------+----------------+
| Variable_name                   | Value          |
+---------------------------------+----------------+
| ft_boolean_syntax               | + -><()~*:""&| |
| ft_max_word_len                 | 84             |
| ft_min_word_len                 | 4              |
| ft_query_expansion_limit        | 20             |
| ft_stopword_file                | (built-in)     |
| innodb_ft_aux_table             |                |
| innodb_ft_cache_size            | 8000000        |
| innodb_ft_enable_diag_print     | OFF            |
| innodb_ft_enable_stopword       | ON             |
| innodb_ft_max_token_size        | 84             |
| innodb_ft_min_token_size        | 3              |
| innodb_ft_num_word_optimize     | 2000           |
| innodb_ft_result_cache_limit    | 2000000000     |
| innodb_ft_server_stopword_table |                |
| innodb_ft_sort_pll_degree       | 2              |
| innodb_ft_total_cache_size      | 640000000      |
| innodb_ft_user_stopword_table   |                |
+---------------------------------+----------------+

FULLTEXT索引是按照「詞」進行的索引,MySQL默認的分詞方法是全部非字母和數字的特殊符號都是分詞符(若是但願對中文進行分詞,則可使用MySQL內置的ngram全文檢索插件)。按照分詞方法,"Irving,TX" 將被劃分爲 "Irving" 和 "TX" 兩個詞。 數據庫

再看下配置的內容,其中 innodb_ft_min_token_size 表示最短的索引詞項,也就是隻會對3個英文字符或者3個英文字符以上的關鍵字進行創建索引操做。MySQL不會對"TX"建立索引,這也就是無法搜索到"TX"的數據的緣由。而之因此無法搜索到"Irvin",是由於Fulltext是對「詞」進行構建索引,也就是索引文件中只有」Irving「的索引,沒有「Irvin」的索引。vim

解決方法

方法一: 修改FULLTEXT INDEX配置

修改最小詞項長度爲2,容許對長度爲2的詞進行索引。並使用IN BOOLEAN MODE匹配不完整單詞。優化

修改最小詞項長度爲2,容許對長度爲2的詞進行索引

以爲1過小了,通常的單詞都不會是一個字母的,並且若是這個數值過小,會致使索引文件過大,不利於索引的更新。於是修改成2就好了。在MyISAM數據庫引擎中使用的是ft_min_word_len,而InnoDB中使用的是innodb_ft_min_token_sizeui

在修改以前執行,即便數據庫中含有state=TX的數據,查詢的結果仍是會爲空。搜索引擎

select * from Address where match(address, city, state, country, zip_code) against ('TX');
  1. 修改 my.cnf,在 [mysqld] 後面加入配置項。.net

    sudo vim /etc/mysql/my.cnf

    配置內容

    innodb_ft_min_token_size=2
    ft_min_word_len=2
  2. 重啓mysql服務。

    sudo service mysql restart
  3. 從新構建索引文件。

    對於使用MyISAM的表須要手動修復。詳細見:what to do when ' repair table ' query won't work in mysql? 以及 Fine-Tuning MySQL Full-Text Search或者其中文翻譯微調MySQL全文搜索

    REPAIR TABLE Address QUICK;

    而對於使用InnoDB的表,可使用以下指令對錶進行索引的從新構建。該操做會獲取到表的讀鎖。

    ALTER TABLE Address ENGINE=INNODB;

    使用優化指令也能夠起到一樣的做用,同時這個指令會完成更多的優化做用。OPTIMIZE TABLE運行過程當中,MySQL會鎖定表。

    OPTIMIZE TABLE Project;
    -- 執行以後會返回以下信息,但其實是執行成功的
    -- Table does not support optimize, doing recreate + analyze instead
  4. 查看是否生效。

    show variables like 'innodb_ft_min_token_size'; 
    show variables like 'ft_min_word_len';

    在修改以後執行,若是數據庫中含有state=TX的數據都會被查詢出來。

    select * from Address where match(address, city, state, country, zip_code) against ('TX');
使用 IN BOOLEAN MODE匹配不完整單詞

用戶輸入的內容的順序爲從左到右輸入,也就是若是用戶要輸入多個單詞,那麼最左側的單詞必然是完整的,最右側的單詞多是不完整的。那麼能夠在用戶輸入的檢索地址末尾添加 * 通配符,使得能夠匹配不完整單詞。能夠修改成:

select * from Address where match(address, city, state, country, zip_code) against ('TX,Irvin*' IN BOOLEAN MODE);

該方法依賴於MySQL自身的配置,若是肯定要使用FULLTEXT INDEX,應該在建立數據庫的時候就配置好須要的配置,以避免影響已經上線的系統。

注意:若是用戶自己的輸入中也含有了ft_boolean_syntax指定的通配符,那麼咱們須要在程序中先進行一次格式化,以防出現非法查詢,如 「Irvin**」 等。或者也能夠限制ft_boolean_syntax的字符。

方法二: 使用LIKE作匹配

曲線救國,使用 Like 代替 FULLTEXT INDEX。在程序中按照MySQL的方法進行分詞,並在每一個單詞之間增長通配符 %

select * from Address where concat(address, ',', city, ',', state, ' ', zip_code, ' ', country) like '%Irvin%TX%';

使用該方法,將會失去對地址信息的索引,並由於使用了 Like,且查詢的內容是以而致使須要對整表進行掃描。此外,Like 是沒有匹配對度的,也就是結果的順序將和匹配度無關。

Like 只有在非通配符開始的語句中才會使用到索引,如: "Irvin%" 將會使用索引,而 "%Irvin" 則不會使用到索引。

方法三: 增長 full_address 字段,並使用LIKE作匹配

添加一個完整的 full_address 字段,full_address 值爲 address, city, state zip_code, country

select * from Address where full_address like '%Irvin%TX%';

該方法主要是利用空間換時間,解決了方法二中每次查詢都須要拼接字符串的耗時操做。

方法四: 使用專業的搜索引擎

使用更爲專業的搜索引擎,如 Elasticsearch 或者 Solr。

參考

相關文章
相關標籤/搜索