乐趣区

关于GaussDB:一文掌握GaussDBDWS-SQL进阶技能全文检索

摘要:本文简要介绍了 GaussDB(DWS)全文检索的原理和应用办法。

本文分享自华为云社区《GaussDB(DWS) SQL 进阶之全文检索》,原文作者:Zhang Jingyao。

全文检索(Text search)顾名思义,就是在给定的文档中查找指定模式(pattern)的过程。GaussDB(DWS)反对对表格中文本类型的字段及字段的组合做全文检索,找出能匹配给定模式的文本,并以用户冀望的形式将匹配后果出现进去。

本文联合笔者的教训和思考,对 GaussDB(DWS)的全文检索性能作简要介绍,心愿能对读者有所帮忙。

1. 预处理

在指定的文档中查找一个模式有很多种方法,例如能够用 grep 命令搜寻一个正则表达式。实践上,对数据库中的文本字段也能够用相似 grep 的形式来检索模式,GaussDB(DWS)中就能够通过关键字“LIKE”或操作符“~”来匹配字符串。但这样做有很多问题。首先对每段文本都要扫描,效率比拟低,难以掂量“匹配度”或“相关度”。而且只能机械地匹配字符串,短少对语法语义的剖析能力,例如对英语中的名词复数,动词的时态变换等难以主动地辨认和匹配,对于由自然语言形成的文本无奈取得令人满意的检索后果。

GaussDB(DWS)采纳相似搜索引擎的形式来进行全文检索。首先对给定的文本和模式做预处理,包含从一段文本中提取出单词或词组,去掉对检索无用的停用词(stop word),对变形后的单词做标准化等等,使之变为适宜检索的模式再作匹配。

GaussDB(DWS)中,原始的文档和搜寻条件都用文本(text)示意,或者说,用字符串示意。通过预处理后的文档变为 tsvector 类型,通过函数 to_tsvector 来实现这一转换。例如,

postgres=# select to_tsvector('a fat cat ate fat rats');
            to_tsvector           
-----------------------------------
 'ate':4 'cat':3 'fat':2,5 'rat':6
(1 row)

察看下面输入的 tsvector 类型,能够看到 to_tsvector 的成果:

  • 首先 各个单词被摘取进去,其地位用整数标识进去,例如“fat”位于原始句子中的第 2 和第 5 个词的地位。
  • 此外 ,“a”这个词太常见了,简直每个文档里都会呈现,对于检索到有用的信息简直没有帮忙。套用香农实践,一个词呈现的概率越大,其蕴含的信息量越小。像“a”,“the”这种单词简直不携带任何信息,所以被当做 停用词(stop word)去掉了。留神这并没有影响其余词的地位编号,“fat”的地位依然是 2 和 5,而不是 1 和 4。
  • 另外 ,复数模式的“rats”被换成了复数模式“rat”。这个操作被称为 标准化 (Normalize),次要是针对西文中单词在不同语境中会产生的变形,去掉后缀保留词根的一种操作。其意义在于简化自然语言的检索,例如检索“rat”时能够将蕴含“rat”和“rats”的文档都检索进去。被标准化后失去的单词称为 词位 (lexeme),比方“rat”。而原始的单词被称为 语言符号(token)。

将一个文档转换成 tsvector 模式有很多益处。例如,能够不便地创立索引,进步检索的速度和效率,当文档数量微小时,通过索引来检索关键字比 grep 这种全文扫描匹配要快得多。再比方,能够对不同关键字按重要水平调配不同的权重,不便对检索后果进行排序,找出相关度最高的文档等等。

通过预处理后的检索条件被转换成 tsquery 类型,可通过 to_tsquery 函数实现。例如,

postgres=# select to_tsquery('a & cats & rat');
  to_tsquery  
---------------
 'cat' & 'rat'
(1 row)

从下面的例子能够看到:

  • 跟 to_tsvector 相似,to_tsquery 也会对输出文本做去掉停用词、标准化等操作,例如去掉了“a”,把“cats”变成“cat”等。
  • 输出的检索条件自身必须用与(&)、或(|)、非(!)操作符连贯,例如上面的语句会报错
postgres=# select to_tsquery('cats rat');
ERROR:  syntax error in tsquery: "cats rat"
CONTEXT:  referenced column: to_tsquery

但 plainto_tsquery 没有这个限度。plainto_tsquery 会把输出的单词变成“与”条件:

postgres=# select plainto_tsquery('cats rat');
 plainto_tsquery
-----------------
 'cat' & 'rat'
(1 row)
postgres=# select plainto_tsquery('cats,rat');
 plainto_tsquery
-----------------
 'cat' & 'rat'
(1 row)

除了用函数之外,还能够用强制类型转换的形式将一个字符串转换成 tsvector 或 tsquery 类型,例如

postgres=# select 'fat cats sat on a mat and ate a fat rat'::tsvector;
                      tsvector                      
-----------------------------------------------------
 'a' 'and' 'ate' 'cats' 'fat' 'mat' 'on' 'rat' 'sat'
(1 row)
postgres=# select 'a & fat & rats'::tsquery;
       tsquery       
----------------------
 'a' & 'fat' & 'rats'
(1 row)

跟函数的区别是强制类型转换不会去掉停用词,也不会作标准化,且对于 tsvector 类型不会记录词的地位。

2. 模式匹配

把输出文档和检索条件转换成 tsvector 和 tsquery 之后,就能够进行模式匹配了。GaussDB(DWS)中应用“@@”操作符来进行模式匹配,胜利返回 True,失败返回 false。

例如创立如下表格,

postgres=# create table post(
postgres(# id bigint,
postgres(# author name,
postgres(# title text,
postgres(# body text);
CREATE TABLE
-- insert some tuples

而后想检索 body 中含有“physics”或“math”的帖子题目,能够用如下的语句来查问:

postgres=# select title from post where to_tsvector(body) @@ to_tsquery('physics | math');
            title           
-----------------------------
 The most popular math books

也能够将多个字段组合起来查问:

postgres=# select title from post where to_tsvector(title || '' || body) @@ to_tsquery('physics | math');
            title           
-----------------------------
 The most popular math books
(1 row)

留神不同的查问形式可能产生不同的后果。例如上面的匹配不胜利,因为::tsquery 没对检索条件做标准化,后面的 tsvector 里找不到“cats”这个词:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cats & rat'::tsquery;
 ?column?
----------
 f
(1 row)

而同样的文档和检索条件,上面的匹配能胜利,因为 to_tsquery 会把“cats”变成“cat”:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ to_tsquery('cats & rat');
 ?column?
----------
 t
(1 row)

相似地,上面的匹配不胜利,因为 to_tsvector 会把停用词 a 去掉:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cat & rat & a'::tsquery;
 ?column?
----------
 f
(1 row)

而上面的能胜利,因为::tsvector 保留了所有词:

postgres=# select 'a fat cat ate fat rats'::tsvector @@ 'cat & rat & a'::tsquery;
 ?column?
----------
 f
(1 row)

所以应依据须要抉择适合的检索形式。

此外,@@操作符能够对输出的 text 做隐式类型转换,例如,

postgres=# select title from post where body @@ 'physics | math';
 title
-------
(0 rows)

精确来讲,text@@text 相当于 to_tsvector(text) @@ plainto_tsquery(text),因而下面的匹配不胜利,因为 plainto_tsquery 会把或条件 ’physics | math’ 变成与条件 ’physic’ & ‘math’。应用时要分外小心。

3. 创立和应用索引

前文提到,一一扫描表中的文本字段迟缓低效,而索引查找可能进步检索的速度和效率。GaussDB(DWS)反对用通用倒排索引 GIN(Generalized Inverted Index)进行全文检索。GIN 是搜索引擎中罕用的一种索引,其次要原理是通过关键字反过来查找所在的文档,从而进步查问效率。可通过以下语句在 text 类型的字段上创立 GIN 索引:

postgres=# create index post_body_idx_1 on post using gin(to_tsvector('english', body));
CREATE INDEX

留神这里必须应用 to_tsvector 函数生成 tsvector,不能应用强制或隐式类型转换。而且这里用到的 to_tsvector 函数比前一节多了一个参数’english’,这个参数是用来指定文本搜寻配置(Text search Configuration)的。对于文本搜寻配置将在下一节介绍。不同的配置计算出来的 tsvector 不同,生成的索引天然也不同,所以这里必须明确指定,而且在查问的时候只有配置和字段都与索引定义统一能力通过索引查找。例如上面的查问中,前一个能够通过 post_body_idx_1 来检索,后一个找不到对应的索引,只能通过全表扫描检索。

postgres=# explain select title from post where to_tsvector('english', body) @@ to_tsquery('physics | math');
                                             QUERY PLAN                                             
-----------------------------------------------------------------------------------------------------
  id |            operation            | E-rows | E-width | E-costs
 ----+---------------------------------+--------+---------+---------
   1 | ->  Streaming (type: GATHER)    |      1 |      32 | 42.02  
   2 |    ->  Bitmap Heap Scan on post |      1 |      32 | 16.02  
   3 |       ->  Bitmap Index Scan     |      1 |       0 | 12.00  
postgres=# explain select title from post where to_tsvector('french', body) @@ to_tsquery('physics | math');
                                          QUERY PLAN                                         
----------------------------------------------------------------------------------------------
  id |          operation           | E-rows | E-width |     E-costs     
 ----+------------------------------+--------+---------+------------------
   1 | ->  Streaming (type: GATHER) |      1 |      32 | 1000000002360.50
   2 |    ->  Seq Scan on post      |      1 |      32 | 1000000002334.50

4. 全文检索配置(Text search Configuration)

这一节谈谈 GaussDB(DWS)如何对文档做预处理,或者说,to_tsvector 是如何工作的。

文档预处理大体上分如下三步进行:

  • 第一步,将文本中的单词或词组一个一个提取进去。这项工作由 解析器 (Parser)或称 分词(Segmentation)器来进行。实现后文档变成一系列 token。
  • 第二步,对上一步失去的 token 做标准化,包含根据指定的规定去掉前后缀,转换同义词,去掉停用词等等,从而失去一个个 词位 (lexeme)。这一步操作根据 词典(Dictionary)来进行,也就是说,词典定义了标准化的规定。
  • 最初,记录各个词位的地位(和权重),从而失去 tsvector。

从下面的形容能够看出,如果给定了解析器和词典,那么文档预处理的规定也就确定了。在 GaussDB(DWS)中,这一整套文档预处理的规定称为 全文检索配置(Text search Configuration)。全文检索配置决定了匹配的后果和品质。

如下图所示,一个全文检索配置由 一个 解析器和 一组 词典组成。输出文档首先被解析器分解成 token,而后对每个 token 一一词典查找,如果在某个词典中找到这个 token,就依照该词典的规定对其做 Normalize。有的词典做完 Normalize 后会将该 token 标记为“已解决”,这样前面的字典就不会再解决了。有的词典做完 Normalize 后将其输入为新的 token 交给前面的词典解决,这样的词典称为“过滤型”词典。

图 1 文档预处理过程

配置应用的解析器在创立配置的时候指定,且不可批改,例如,

postgres=# create text search configuration mytsconf (parser = default);
CREATE TEXT SEARCH CONFIGURATION

GaussDB(DWS)内置了 4 种解析器,目前不反对自定义解析器。

postgres=# select prsname from pg_ts_parser;
 prsname 
----------
 default
 ngram
 pound
 zhparser
(4 rows)

词典则通过 ALTER TEXT SEARCH CONFIGURATION 命令来指定,例如

postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION

postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION

指定了 mytsconf 应用 english_stem 和 simple 这两种词典来对“asciiword”类型的 token 做标准化。

下面语句中的“asciiword”是一种 token 类型。解析器会对合成出的 token 做分类,不同的解析器分类形式不同,可通过 ts_token_type 函数查看。例如,‘default’解析器将 token 分为如下 23 种类型:

postgres=# select * from ts_token_type('default');
 tokid |      alias      |               description                
-------+-----------------+------------------------------------------
     1 | asciiword       | Word, all ASCII
     2 | word            | Word, all letters
     3 | numword         | Word, letters and digits
     4 | email           | Email address
     5 | url             | URL
     6 | host            | Host
     7 | sfloat          | Scientific notation
     8 | version         | Version number
     9 | hword_numpart   | Hyphenated word part, letters and digits
    10 | hword_part      | Hyphenated word part, all letters
    11 | hword_asciipart | Hyphenated word part, all ASCII
    12 | blank           | Space symbols
    13 | tag             | XML tag
    14 | protocol        | Protocol head
    15 | numhword        | Hyphenated word, letters and digits
    16 | asciihword      | Hyphenated word, all ASCII
    17 | hword           | Hyphenated word, all letters
    18 | url_path        | URL path
    19 | file            | File or path name
    20 | float           | Decimal notation
    21 | int             | Signed integer
    22 | uint            | Unsigned integer
    23 | entity          | XML entity
(23 rows)

以后数据库中已有的词典能够通过零碎表 pg_ts_dict 查问。

如果指定了配置,零碎会依照指定的配置对文档作预处理,如上一节创立 GIN 索引的命令。如果没指定配置,to_tsvector 应用 default_text_search_config 变量指定的默认配置。

postgres=# show default_text_search_config; -- 查看以后默认配置
 default_text_search_config
----------------------------
 pg_catalog.english
(1 row)
postgres=# set default_text_search_config = mytsconf;  -- 设置默认配置
SET
postgres=# show default_text_search_config;
 default_text_search_config
----------------------------
 public.mytsconf
(1 row)
postgres=# reset default_text_search_config;  -- 复原默认配置
RESET
postgres=# show default_text_search_config;
 default_text_search_config
----------------------------
 pg_catalog.english
(1 row)

留神 default_text_search_config 是一个 session 级的变量,只在以后会话中无效。如果想让默认配置长久失效,能够批改 postgresql.conf 配置文件中的同名变量,如下图所示。

批改后须要重启过程。

总结

GaussDB(DWS)的全文检索模块提供了弱小的文档搜寻性能。相比于用“LIKE”关键字,或“~”操作符的模式匹配,全文检索提供了较丰盛的语义语法反对,能对自然语言文本做更加智能化的解决。配合失当的索引,可能实现对文档的高效检索。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版