暂无图片
暂无图片
5
暂无图片
暂无图片
暂无图片

「瀚高数据库技术栈」瀚高数据库的全文(中文)检索

瀚高数据库 2021-09-27
2583
目录

1 数据分类
2 瀚高的全文检索简介
3 瀚高全文检索分词简介
4 瀚高全文检索常见索引
    4.1 GIN索引结构
    4.2 RUM索引
5 基于词库的解决方案
    5.1 基于SCWS分词
    5.2 基于pg_jieba 分词
6 基于二元分词的解决方案
A 测试用例
【基于SCWS分词的全文检索测试用例】
【基于pg_jieba的全文检索测试用例】
【基于二元分词的全文检索测试用例】

数据分类

数据是数据库中存储的基本对象,凡是计算机中用来描述事物的记录都可以统称为数据,包括数字、文字、图形、图像、音频、视频、学生的档案记录等等,可以分为两类:结构化数据、非结构化数据。

结构化数据是指可以使用关系型数据库表示和存储,表现为二维形式的数据。一般特点是:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。结构化的数据的存储和排列是很有规律的,这对查询和修改等操作很有帮助,但是它的扩展性不好。
 非结构化数据是没有固定结构的数据。对于这类数据,我们一般直接整体进行存储。包括文字、文件、图片、音频和视频等。本文所涉及的专指文字,特别是一些较大的文档,例如文章、论文、新闻报道等。
数据分类
概念
检索方式
结构化数据
有固定格式或者有限长度
参照传统关系型数据库
非结构化数据
不定长或者无固定格式
顺序查找
全文检索

瀚高的全文检索简介

全文检索(或者文本搜索)提供了确定满足一个查询的自然语言文档的能力,并可以选择将它们按照与查询的相关度排序。最常用的搜索类型是找到所有包含给定查询词的文档并按照它们与查询的相似性顺序返回它们。为实现全文检索,第一种是全量扫描逐行匹配,另一种是通过构建索引加快查询的速度。
全文索引的创建可分为两步,第一步对文档的预处理(也可称为矢量化、分词),第二创建索引。
预处理是指将文档解析成记号、再将记号转换成词位。把原始文档标识成多种类型的记号,例如数字、词、复杂的词、电子邮件地址等,这样它们可以被以不同的方式处理。瀚高数据库使用解析器来执行这个步骤,并且支持创建定制的解析器。词位是一个字符串,它已经被正规化,包含了各个记号的位置信息。瀚高数据库使用词典来执行这个步骤,同样支持创建定制的词典。此外,这个步骤通常会消除停用词,它们是那些太普通的词,它们对于搜索是无用的。如下一段话:“今天我们相聚在这里,共同迎接美好的生活”,通过预处理可以解析成“'生活':4 '相聚':1 '美好':3 '迎接':2”这样的字符串,各个词后的数字就是指在文档中出现的位置,类似于“我们在这里”等普通词会被过滤掉。
瀚高数据库提供了tsvector、tsquery两种数据类型用来支持全文检索。其中tsvector中存储预处理之后的数据,tsquery是用来匹配查询条件的。具体用法后续会介绍。
对文档进行预处理之后就可以对预处理之后的字符串创建索引,瀚高数据库支持创建多种索引,常见的索引包括GIN索引、RUM索引。

3 瀚高全文检索分词简介

全文索引的一个前提是对于复杂文本进行分词,即提取Key。瀚高支持多种分词系统,两种常用的分词系统:SCWS分词、pg_jieba分词、二元分词。
SCWS分词
SCWS (Simple Chinese Words Segmentation)即简易中文分词系统。这是一套基于词频词典的机械中文分词引擎,它能将一整段的汉字基本正确的切分成词。词是汉语的基本语素单位,而书写的时候不像英语会在词之间用空格分开。SCWS 采用的是自行采集的词频词典,并辅以一定程度上的专有名称、人名、地名、数字年代等规则集,切词效率高。瀚高支持zhparser和SCWS搭配使用。
pg_jieba分词
    jieba号称是Python中最好的中分词库。基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能词情况所构成的有向无环图;采用动态规划查找最大概率路径,找出基于词频的最大切分组合;对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法。
二元分词
顾名思义,是将文档中的字进行前后两两结合,具体“我在济南大明湖畔”可以分词称为“我在、在济、济南、南大、”,当然也是可以设置N元分词(N一般为1~10)。

4 瀚高全文检索常见索引

4.1 GIN索引结构

GIN索引(Generalized Inverted Index, 通用倒排索引)是一个存储对(key, posting list)集合的索引结构,其中key是一个键值,而posting list 是一组出现过key的位置。如(‘祖国', '14:2 23:4')中,表示“祖国”在14:2和23:4这两个位置出现过,在瀚高数据库中这些位置实际上就是元组的TID(行号:Data Block ID(32bit)+ Item Point(16 bit) )。表中的每一个属性,在建立索引时,都可能会被解析为多个键值,所以同一个元组的TID可能会出现在多个key的posting list中。

4.1.1逻辑结构

GIN索引在逻辑上可以看成一个relation,该relation有两种结构:
1.只索引基表的一列
key
value
Key1
Posting list( or posting tree)
Key2
Posting list( or posting tree)
2. 索引基表的多列(复合、多列索引)
column_id
key
value
Column1 num
Key1
Posting list( or posting tree)
Column2 num
Key1
Posting list( or posting tree)
Column3 num
Key1
Posting list( or posting tree)
...
...
...
这种结构,对于基表中不同列的相同的key,在GIN索引中也会当作不同的key来处理。

4.1.2物理结构

GIN索引在物理存储上包含如下内容:
Entry
GIN索引中的一个元素,可以认为是一个词位,也可以理解为一个key
Entry tree
在Entry上构建的B树
Posting List
一个Entry出现的物理位置(heap ctid, 堆表行号)的链表
Posting Tree
在一个Entry出现的物理位置链表(heap ctid, 堆表行号)上构建的B树,所以posting tree的Key是ctid,而entry tree的Key是被索引的列的值
Pending List
索引元组的临时存储链表,用于fastupdate模式的插入操作
从上面可以看出GIN索引主要由Entry tree和Posting Tree(or Posting List)组成,其中Entry tree是GIN索引的主结构树,Posting Tree是辅助树。Entry Tree类似于B+tree,而Posting Tree则类似于B-tree。另外,不管Entry Tree还是Posting Tree,它们都是按KEY有序组织的,如下图示。

4.2 RUM索引

RUM索引与GIN类似,但是在Posting List|Tree的每一个ctid后面会追加一些属性值,例如(‘祖国', '14:2:100')表示“祖国”在14:2 这个位置出现了100次,这个频次就是一种额外的信息,在处理排序或者统计出现频次的时候,性能很优异。可以说RUM索是对GIN索引的一种加强,但是RUM索引的膨胀率要比GIN索引高,维护索引会消耗更多的性能。

5 基于词库的解决方案

5.1 基于SCWS分词
基于SCWS分词的全文索引会用到SCWS和zhparser两个插件。
5.1.1 安装SCWS
    #Step1:Downloads
    wget http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2
    #Step2:在pg的安装目录下创建文件夹swcs,解压
    tar xvjf scws-1.2.3.tar.bz2
    #step3:安装
    /configure --prefix=/usr/local/scws ; make ; make install
    #Step4:下载解压词典
    cd /usr/local/scws/etc
    wget http://www.xunsearch.com/scws/down/scws-dict-chs-gbk.tar.bz2
    wget http://www.xunsearch.com/scws/down/scws-dict-chs-utf8.tar.bz2
    tar xvjf scws-dict-chs-gbk.tar.bz2
    tar xvjf scws-dict-chs-utf8.tar.bz2
    5.1.2 安装zhparser
      #Step1:在pg的安装目录下打开文件夹scws
      cd scws
      #Step2:把压缩包上传到该目录里面,然后解压
      unzip zhparser-master.zip
      #Step3:进入到解压的目录
      cd zhparser-master
      #Step4:进行安装
      SCWS_HOME=/usr/local make&&make install
      5.2 基于pg_jieba 分词
        #可以在瀚高的安装目录或者root/pg_jieba。
        #Step1:Downloads
        git clone https://github.com/jaiminpan/pg_jieba
        #Step2:Compilecd
        pg_jiebaUSE_PGXS=1 makeUSE_PGXS=1 make install
        # if got error when doing "USE_PGXS=1 make install"# try "sudo USE_PGXS=1 make install"

        6 基于二元分词的解决方案

        瀚高数据库默认支持二元分词,不需要其它插件。

        6.1 使用步骤

          CREATE TABLE test_rum(id int, message text);
          CREATE INDEX rumidx ON test_rum USING rum(message rum_text_ops);
          插入数据:
            INSERT INTO test_rum VALUES (1,'我在青岛');
            INSERT INTO test_rum VALUES (2,'我在济南大明湖');
            查询数据:
              set enable_seqscan=OFF;
              select * from test_rum WHERE message % '青岛';
              select * from test_rum WHERE message % '我在青岛';

              6.2 操作符

              (1) 匹配符号 %
              当要在表中的某一列检索某个词时,需要使用百分号%来表示该检索。百分号前面指定一个数据列,后面指定一个查询条件。百分号后面的查询条件可以是多个词,词与词之间可以是:与关系(&),或关系(|)。例如:
                select * from test_rum WHERE message % '青岛 | 济南';
                select * from test_rum WHERE message % '济南 & 大明湖';
                一个词不能有空格,否则语法检查会报错,查询也不会执行。如果需要查询多个词,必须使用逻辑符号( “|”,”&”)来关联多个词。
                (2)相似比较 like
                LIKE 的引入,主要是为了在语法上支持非索引情况下的 LIKE。
                  select * from test_rum WHERE message LIKE '青岛';
                  select * from test_rum WHERE message LIKE '%青岛';
                  在 zhfts 索引中,其处理方式类似于%。zhfts 把检索条件中的百分号%去掉,对剩
                  余的文本做二元分词,把分词的结果作为 key,检索索引。
                  (3)相似度和 order by <->
                  检索中,检索条件和某行的列数据匹配,则该行数据符合条件,会出现在检索结果中。有些情况下,需要对检索结果排序,相似度高的数据排列在前面,则需要使用:order by <->。
                  例如:
                  select * from test_rum WHERE message % '我在青岛' order by message <->'我在青岛';
                  其中,message <->'我在青岛',会调用内置函数,计算 2 个参数之间的相似度,返
                  回一个浮点数。数值越小,越相似。默认情况下,越相似的匹配就会排到前面。

                  A 测试用例

                  【基于SCWS分词的全文检索测试用例】

                  1.创建zhparser解析器
                  连接到目标数据库,创建zhparser解析器
                    create extension zhparser;
                    2.将zhparser解析器作为全文检索配置项
                      create text search configuration chinese (PARSER = zhparser);
                      #使用 \dFp查看解析器
                        \dFp
                        zhparser可以将中文切分成26中token
                          select ts_token_type('zhparser');
                          3.指定分词策略
                          通常情况:只需要按照名词(n),动词(v),形容词(a),成语(i),叹词(e)和习用语(l)6种方式对句子进行划分就可以
                            alter text search configuration chinese add mapping for n,v,a,i,e,l with simple;
                            4. 建表
                              create table gin_zhparser_test (id integer,  title varchar(50),content text,  primary key(id),  tsv_content tsvector);
                              5. 插入数据
                                insert into gin_zhparser_test values(generate_series(1,100000),'《丑小鸭》''一只只小鸭子都从蛋壳里钻出来了,就剩下一个特别大的蛋。过了好几天,这个蛋才慢慢裂开,钻出一只又大又丑的鸭子。他的毛灰灰的,嘴巴大大的,身子瘦瘦的,大家都叫他“丑小鸭”。 丑小鸭来到世界上,除了鸭妈妈,谁都欺负他。哥哥、姐姐咬他,公鸡啄他,连养鸭的小姑娘也讨厌他。丑小鸭感到非常孤单,就钻出篱笆,离开了家。 丑小鸭来到树林里,小鸟讥笑他,猎狗追赶他。他白天只好躲起来,到了晚上才敢出来找吃的。 秋天到了,树叶黄了,丑小鸭来到湖边的芦苇里,悄悄地过日子。一天傍晚,一群天鹅从空中飞过。丑小鸭望着洁白美丽的天鹅,又惊奇又羡慕。 天越来越冷,湖面结了厚厚的冰。丑小鸭趴在冰上冻僵了。幸亏一位农夫看见了,把他带回家。 一天,丑小鸭出来散步,看见丁香开花了,知道春天来了。他扑扑翅膀,向湖边飞去,忽然看见镜子似的湖面上,映出一个漂亮的影子,雪白的羽毛,长长的脖子,美丽极了。这难道是自己的影子?啊,原来我不是丑小鸭,是一只漂亮的天鹅呀! );。
                                最后再插入第100001条数据,文档最后加一句话今天我们相聚在这里,共同迎接美好的生活
                                6. 不建索引的时候查询一个like
                                  explain analyze select * from gin_zhparser_test where content like '%迎接%美好生活%';
                                  7. 更新全文索引列
                                    update gin_zhparser_test
                                    set tsv_content = to_tsvector('chinese', coalesce(content,''));
                                    8. 创建索引
                                      Create index idx_gin_test on gin_zhparser_test using gin(tsv_content);
                                      9. 查看执行计划
                                        explain analyze select * from gin_zhparser_test where tsv_content @@ plainto_tsquery('迎接美好生活');
                                        【基于pg_jieba的全文检索测试用例】
                                        1.连接数据库创建pg_jieba的解析器
                                          create extension pg_jieba;
                                          创建完pg_jieba解析器以后会自动就有一个jiebacfg的中文检索配置。
                                          2.建表
                                            create table pg_jieba_test (id integer,  title varchar(50),content text,  primary key(id),  tsv_content tsvector);
                                            3.插入数据
                                              insert into pg_jieba_test values((generate_series(1,100000),'《丑小鸭》', '一只只小鸭子都从蛋壳里钻出来了,就剩下一个特别大的蛋。过了好几天,这个蛋才慢慢裂开,钻出一只又大又丑的鸭子。他的毛灰灰的,嘴巴大大的,身子瘦瘦的,大家都叫他“丑小鸭”。丑小鸭来到世界上,除了鸭妈妈,谁都欺负他。哥哥、姐姐咬他,公鸡啄他,连养鸭的小姑娘也讨厌他。丑小鸭感到非常孤单,就钻出篱笆,离开了家。丑小鸭来到树林里,小鸟讥笑他,猎狗追赶他。他白天只好躲起来,到了晚上才敢出来找吃的。秋天到了,树叶黄了,丑小鸭来到湖边的芦苇里,悄悄地过日子。一天傍晚,一群天鹅从空中飞过。丑小鸭望着洁白美丽的天鹅,又惊奇又羡慕。天越来越冷,湖面结了厚厚的冰。丑小鸭趴在冰上冻僵了。幸亏一位农夫看见了,把他带回家。一天,丑小鸭出来散步,看见丁香开花了,知道春天来了。他扑扑翅膀,向湖边飞去,忽然看见镜子似的湖面上,映出一个漂亮的影子,雪白的羽毛,长长的脖子,美丽极了。这难道是自己的影子?啊,原来我不是丑小鸭,是一只漂亮的天鹅呀!');
                                              最后再插入第100001条数据,文档最后加一句话“今天我们相聚在这里,共同迎接美好的生活”。
                                              4.不建索引时候查询一个like
                                                explain analyze select * from pg_jieba_test where content like '%迎接美好生活%';
                                                5.更新全文索引列
                                                  update pg_jieba_test set tsv_content = to_tsvector('jiebacfg'coalesce(content,''));
                                                  6.创建索引
                                                    create index idx_pgjieba_test on pg_jieba_test using gin(tsv_content);
                                                    7.查看执行计划
                                                      explain analyze select * from pg_jieba_test where tsv_content @@ plainto_tsquery '迎接美好生活';

                                                      【基于二元分词的全文检索测试用例】

                                                      1.建表
                                                        CREATE TABLE rum_test(id int, message text);
                                                        2.插入数据:
                                                          insert into rum_tes values((generate_series(1,100000), '《丑小鸭》 一只只小鸭子都从蛋壳里钻出来了,就剩下一个特别大的蛋。过了好几天,这个蛋才慢慢裂开,钻出一只又大又丑的鸭子。他的毛灰灰的,嘴巴大大的,身子瘦瘦的,大家都叫他“丑小鸭”。丑小鸭来到世界上,除了鸭妈妈,谁都欺负他。哥哥、姐姐咬他,公鸡啄他,连养鸭的小姑娘也讨厌他。丑小鸭感到非常孤单,就钻出篱笆,离开了家。丑小鸭来到树林里,小鸟讥笑他,猎狗追赶他。他白天只好躲起来,到了晚上才敢出来找吃的。秋天到了,树叶黄了,丑小鸭来到湖边的芦苇里,悄悄地过日子。一天傍晚,一群天鹅从空中飞过。丑小鸭望着洁白美丽的天鹅,又惊奇又羡慕。天越来越冷,湖面结了厚厚的冰。丑小鸭趴在冰上冻僵了。幸亏一位农夫看见了,把他带回家。一天,丑小鸭出来散步,看见丁香开花了,知道春天来了。他扑扑翅膀,向湖边飞去,忽然看见镜子似的湖面上,映出一个漂亮的影子,雪白的羽毛,长长的脖子,美丽极了。这难道是自己的影子?啊,原来我不是丑小鸭,是一只漂亮的天鹅呀!');
                                                          最后再插入第100001条数据,文档最后加一句话今天我们相聚在这里,共同迎接美好的生活
                                                          3.不使用索引查询数据
                                                            explain analyze select * from rum_test where message like '%迎接美好生活%';
                                                            4.创建索引
                                                              CREATE INDEX idx_rumtest ON rum_test USING zhfts (message zhfts_text_ops);
                                                              5.使用索引查询数据
                                                                explain analyze select * from rum_test where message % '迎接美好生活';
                                                                文章转载自瀚高数据库,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                评论