mysql模糊查詢再也不用like+%了

mysql模糊查詢再也不用like+%了

我們都知道 innodb 在模糊查詢數據時使用 '%xx' 會導致索引失效,但有時需求就是如此,類似這樣的需求還有很多

最后更新 2022/4/15 下午9:31
_沸羊羊_
预计阅读 10 分钟
分类
MySQL
标签
MySQL 模擬查詢

前言

我們都知道 innodb 在模糊查詢數據時使用 "%xx" 會導致索引失效,但有時需求就是如此,類似這樣的需求還有很多,例如,搜尋引擎需要根據用戶數據的關鍵字進行全文查找,電子商務網站需要根據用戶的查詢條件,在可能需要在商品的詳細居間中進行查找,這些都不是 b+樹索引能很好完成的工作。

通過數值比較,範圍過濾等就可以完成絕大多數我們需要的查詢了。但是,如果希望通過關鍵字的匹配來進行查詢過濾,那麼就需要基於相似度的查詢,而不是原來的精確數值比較,全文索引就是為這種場景設計的。

全文索引(full-text search)是將存儲於資料庫中的整本書或整篇文章中的任意信息查找出來的技術。它可以根據需要獲得全文中有關章、節、段、句、詞等信息,也可以進行各種統計和分析。

在早期的 mysql 中,innodb 並不支持全文檢索技術,從 mysql 5.6 開始,innodb 開始支持全文檢索。

倒排索引

全文檢索通常使用倒排索引(inverted index)來實現,倒排索引同 b+tree 一樣,也是一種索引結構。它在輔助表中存儲了單詞與單詞自身在一個或多個文檔中所在位置之間的映射,這通常利用關聯數組實現,擁有兩種表現形式:

  • inverted file index:{單詞,單詞所在文檔的 id}
  • full inverted index:{單詞,(單詞所在文檔的 id,再具體文檔中的位置)}

上圖為 inverted file index 關聯數組,可以看到其中單詞"code"存在於文檔 1,4 中,這樣存儲再進行全文查詢就簡單了,可以直接根據 documents 得到包含查詢關鍵字的文檔;而 full inverted index 存儲的是對,即(documentid,position),因此其存儲的倒排索引如下圖,如關鍵字"code"存在於文檔 1 的第 6 個單詞和文檔 4 的第 8 個單詞。相比之下,full inverted index 占用了更多的空間,但是能更好的定位數據,並擴充一些其他搜索特性。

全文檢索

創建全文索引

1、創建表時創建全文索引語法如下:

CREATE TABLE table_name ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, author VARCHAR(200),
title VARCHAR(200), content TEXT(500), FULLTEXT full_index_name (col_name) ) ENGINE=InnoDB;

輸入查詢語句:

SELECT table_id, name, space from INFORMATION_SCHEMA.INNODB_TABLES
WHERE name LIKE 'test/%';

上述六個索引表構成倒排索引,稱為輔助索引表。當傳入的文檔被標記化時,單個詞與位置信息和關聯的 doc_id,根據單詞的第一個字符的字符集排序權重,在六個索引表中對單詞進行完全排序和分區。

2、在已創建的表上創建全文索引語法如下:

CREATE FULLTEXT INDEX full_index_name ON table_name(col_name);

使用全文索引

mysql 資料庫支持全文檢索的查詢,全文索引只能在 innodb 或 myisam 的表上使用,並且只能用於創建 char,varchar,text 類型的列。

其語法如下:

MATCH(col1,col2,...) AGAINST(expr[search_modifier])
search_modifier:
{
    IN NATURAL LANGUAGE MODE
    | IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
    | IN BOOLEAN MODE
    | WITH QUERY EXPANSION
}

全文搜索使用  MATCH() AGAINST()语法进行,其中,MATCH() 采用逗号分隔的列表,命名要搜索的列。AGAINST()接收一个要搜索的字符串,以及一个要执行的搜索类型的可选修饰符。全文检索分为三种类型:自然语言搜索、布尔搜索、查询扩展搜索,下面将对各种查询模式进行介绍。

Natural Language

自然语言搜索将搜索字符串解释为自然人类语言中的短语,MATCH()默认采用 Natural Language 模式,其表示查询带有指定关键字的文档。

接下來結合 demo 來更好的理解 natural language

SELECT
    count(*) AS count
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( 'MySQL' );

上述語句,查詢 title,body 列中包含 'mysql' 關鍵字的行數量。上述語句還可以這樣寫:

SELECT
    count(IF(MATCH ( title, body )
    against ( 'MySQL' ), 1, NULL )) AS count
FROM
    `fts_articles`;

上述兩種語句雖然得到的結果是一樣的,但從內部運行來看,第二句 sql 的執行速度更快些,因為第一句 sql(基於 where 索引查詢的方式)還需要進行相關性的排序統計,而第二種方式是不需要的。

還可以通過 sql 語句查詢相關性:

SELECT
    *,
    MATCH ( title, body ) against ( 'MySQL' ) AS Relevance
FROM
    fts_articles;

相關性的計算依據以下四個條件:

  • word 是否在文檔中出現
  • word 在文檔中出現的次數
  • word 在索引列中的數量 多少個文檔包含該 word

對於 innodb 存儲引擎的全文檢索,還需要考慮以下的因素:

  • 查詢的 word 在 stopword 列中,忽略該字符串的查詢
  • 查詢的 word 的字符長度是否在區間 [innodb_ft_min_token_size,innodb_ft_max_token_size] 內

如果詞在 stopword 中,則不對該詞進行查詢,如對 'for' 這個詞進行查詢,結果如下所示:

SELECT
    *,
    MATCH ( title, body ) against ( 'for' ) AS Relevance
FROM
    fts_articles;

可以看到,'for'雖然在文檔 2,4 中出現,但由於其是 stopword ,故其相關性為 0

參數 innodb_ft_min_token_size 和 innodb_ft_max_token_size 控制 innodb 引擎查詢字符的長度,當長度小於 innodb_ft_min_token_size 或者長度大於 innodb_ft_max_token_size 時,會忽略該詞的搜索。在 innodb 引擎中,參數 innodb_ft_min_token_size 的默認值是 3,innodb_ft_max_token_size 的默認值是 84

Boolean

布爾搜索使用特殊查詢語言的規則來解釋搜索字符串,該字符串包含要搜索的詞,它還可以包含指定要求的運算符,例如匹配行中必須存在或不存在某個詞,或者它的權重應高於或低於通常情況。例如,下面的語句要求查詢有字符串"pease"但沒有"hot"的文檔,其中+和-分別表示單詞必須存在,或者一定不存在。

select * from fts_test where MATCH(content) AGAINST('+Pease -hot' IN BOOLEAN MODE);

boolean 全文檢索支持的類型包括:

  • +:表示該 word 必須存在
  • :表示該 word 必須不存在
  • (no operator)表示該 word 是可選的,但是如果出現,其相關性會更高
  • @distance 表示查询的多个单词之间的距离是否在 distance 之内,distance 的单位是字节,这种全文检索的查询也称为 Proximity Search,如 MATCH(context) AGAINST('"Pease hot"@30' IN - BOOLEAN MODE)语句表示字符串 Pease 和 hot 之间的距离需在 30 字节内
  • 表示出現該單詞時增加相關性

  • <:表示出現該單詞時降低相關性
  • ~:表示允許出現該單詞,但出現時相關性為負
    • :表示以该单词开头的单词,如 lik*,表示可以是 liklikelikes
  • " :表示短語

下面是一些 demo,看看 boolean mode 是如何使用的。

demo1:+ -

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( '+MySQL -YourSQL' IN BOOLEAN MODE );

上述語句,查詢的是包含 'mysql' 但不包含 'yoursql' 的信息

demo2: no operator

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( 'MySQL IBM' IN BOOLEAN MODE );

上述語句,查詢的 'mysql ibm' 沒有 '+','-'的標識,代表 word 是可選的,如果出現,其相關性會更高

demo3:@

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( '"DB2 IBM"@3' IN BOOLEAN MODE );
复制代码
上述语句,代表 "DB2" ,"IBM"两个词之间的距离在3字节之内

demo4:> <

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( '+MySQL +(>database <DBMS)' IN BOOLEAN MODE );

上述語句,查詢同時包含 'mysql','database','dbms' 的行信息,但不包含'dbms'的行的相關性高於包含'dbms'的行。

demo5: ~

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( 'MySQL ~database' IN BOOLEAN MODE );

上述語句,查詢包含 'mysql' 的行,但如果該行同時包含 'database',則降低相關性。

demo6:*

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( 'My*' IN BOOLEAN MODE );

上述語句,查詢關鍵字中包含'my'的行信息。

demo7:"

SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH ( title, body ) AGAINST ( '"MySQL Security"' IN BOOLEAN MODE );

上述語句,查詢包含確切短語 'mysql security' 的行信息。

Query Expansion

查詢擴展搜索是對自然語言搜索的修改,這種查詢通常在查詢的關鍵詞太短,用戶需要 implied knowledge(隱含知識)時進行,例如,對於單詞 database 的查詢,用戶可能希望查詢的不僅僅是包含 database 的文檔,可能還指那些包含 mysql、oracle、rdbms 的單詞,而這時可以使用 query expansion 模式來開啟全文檢索的 implied knowledge

通過在查詢語句中添加 with query expansion/in natural language mode with query expansion 可以開啟 blind query expansion(又稱為 automatic relevance feedback),該查詢分為兩個階段。

  • 第一階段:根據搜索的單詞進行全文索引查詢
  • 第二階段:根據第一階段產生的分詞再進行一次全文檢索的查詢

接著來看一個例子,看看 query expansion 是如何使用的。

-- 创建索引
create FULLTEXT INDEX title_body_index on fts_articles(title,body);
-- 使用 Natural Language 模式查询
SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH(title,body) AGAINST('database');

使用 query expansion 前查詢結果如下:

-- 当使用 Query Expansion 模式查询
SELECT
    *
FROM
    `fts_articles`
WHERE
    MATCH(title,body) AGAINST('database' WITH QUERY expansion);

使用 query expansion 後查詢結果如下:

由於 query expansion 的全文檢索可能帶來許多非相關性的查詢,因此在使用時,用戶可能需要非常謹慎。

刪除全文索引

1、直接刪除全文索引語法如下:

DROP INDEX full_idx_name ON db_name.table_name;

2、使用 alter table 刪除全文索引語法如下:

ALTER TABLE db_name.table_name DROP INDEX full_idx_name;

小結

本文從理論與實踐結合的角度對 fulltext index 做了居間,如對 mysql 感興趣可繼續關注 mysql 專欄。

Keep Exploring

延伸阅读

更多文章