zoukankan      html  css  js  c++  java
  • [转]PostgreSQL的FTI(TSearch)与中文全文索引的实践

    安装 postgresql

    你可以像平常一样编译和安装 postgresql,使用 tsearch2 进行中文的全文索引的时候,真正的区别发生在初始化数据库的时候。

    初始化数据库

    在linux里面使用tsearch2,首先要把数据库初始化成支持中文的 locale,比如我用 zh_CN.utf8:

    initdb --locale=zh_CN.utf8 --encoding=utf8 ... 

    在一般用途的postgresql的使用时,一般会建议使用 C 做为初始化 locale,这样PG将会使用自身内部的比较函数对各种字符(尤其是中文字符)进行排序,这么做是合适的,因为大量OS的locale实现存在一些问题。对于tsearch2,因为它使用的是locale来进行基础的字串分析的工作,因此,如果错误使用locale,那么很有可能得到的是空字串结果,因为多字节的字符会被当做非法字符过滤掉。

    我正在评估现代 OS/libc 的 locale 的实现是否已经成熟到可以接受的程度。如果是,则我们可以安全地使用 之,如果否,可能我们必须接合 slony 等工具来使用tsearch2。

    安装 tsearch2

    安装 tsearch2很简单,首先:

    cd $PGSRC/contrib/tsearch2

    然后:

    gmake all
    gmake install

    配置tsearch2

    我们需要生成自己的字典和自己的对应locale 的配置选项,所以,要给 tsearch2的表里插入一些数据,简单起见,我先用一个最基本的做演示,将来丰富了中文独立的字典之后,将继续补充:

    --
    -- first you need to use zh_CN.utf8 as locale to initialize your database
    -- initdb -D $YOUR_PG_DATA_DIR --locale zh_CN.utf8 --encoding utf8
    --


    insert into pg_ts_cfg (ts_name, prs_name, locale) values ('utf8_chinese', 'default',
    'zh_CN.utf8');

    insert into pg_ts_dict (dict_name, dict_init, dict_initoption, dict_lexize, dict_comment)
    values ('chinese_stem_utf8', 'snb_ru_init_utf8(internal)', 'contrib/chinese.stop.utf8',
    'snb_lexize(internal,internal,integer)', 'Chinese Stemmer, Laser. UTF8 Encoding');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'lword',
    '{en_stem}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'nlword',
    '{chinese_stem_utf8}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'word',
    '{chinese_stem_utf8}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'email',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'url', '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'host',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'sfloat',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'version',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'part_hword',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'nlpart_hword',
    '{chinese_stem_utf8}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'lpart_hword',
    '{en_stem}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'hword',
    '{chinese_stem_utf8}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'lhword',
    '{en_stem}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'nlhword',
    '{chinese_stem_utf8}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'uri', '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'file',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'float',
    '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'int', '{simple}');

    insert into pg_ts_cfgmap(ts_name,tok_alias, dict_name) values('utf8_chinese', 'uint',
    '{simple}');

    基础的分字程序

    下面是 Carrie (感谢 Carrie! :D )写的一个基础的分字程序,在很大程度上可以满足相当多应用的需要:

    --
    -- a basic Chinese word segment function
    -- author: Carrie
    --

    create or replace function CarrieCharSeg( input text ) returns text as $$
    declare
    retVal text;
    i int;
    j int;
    begin
    i := char_length(input);
    j := 0;
    retVal := '';
    LOOP
    retVal := retVal || substring(input from j for 1) || ' ';

    j := j+1;

    EXIT WHEN j=i+1;
    END LOOP;
    return retVal;
    end;
    $$language plpgsql;

    下面是一个重载的分词程序,区分了单词和汉字,对单词单独进行分词:

    --
    -- a basic Chinese word segment function
    -- author: Carrie
    --
    create or replace function CarrieCharSeg(input text,int) returns text as $Q$
    declare
    query text := '';
    retVal text := '';
    thisVal text := '';
    lastVal text := '';
    i integer := 0;
    j integer := 0;

    begin
    query := lower(regexp_replace(input,'[[:punct:]]',' ','g'));
    --raise notice '123: %',query;
    i := char_length(query);
    LOOP
    thisVal := substring(query from j for 1);
    IF ((thisVal <> ' ')AND(thisVal <> ' ')) THEN
    IF (((lastVal >= 'a') AND (lastVal <= 'z'))
    OR((lastVal >= 'A')AND(lastVal <= 'Z'))) AND
    (((thisVal >= 'a') AND (thisVal <= 'z')) OR
    ((thisVal >= 'A')AND(thisVal <= 'Z'))) THEN
    retVal := retVal || thisVal;
    ELSE
    retVal := retVal || ' ' || thisVal;
    END IF;
    END IF;
    lastVal := thisVal;
    j := j+1;
    EXIT WHEN j > i;
    END LOOP;
    RETURN trim(retVal);
    end
    $Q$language plpgsql;


    使用 tsearch2

    OK,所有东西都安装完毕,我们需要试验一下了,下面是一个基本过程:

    例子1:在原有的表上增加全文索引字段

    准备全文索引的表

    假设我们准备使用全文索引的数据库名字叫 BBS,首先要在这个数据库上安装 tsearch2:

    psql -d BBS -f tsearch2.sql

    在编译完成tsearch2之后,tsearch2.sql 在 $PGSRC/contrib/tsearch2 里面。

    假设我们有这样一个表,里面保存着BBS的帖子:

    create table BbsPost(
    id serial,
    topic varchar(100),
    content text,
    posterId integer,
    postTime timestamp
    );

    增加一个全文索引字段

    现在我们希望我们能够对 topic 字段和 content 字段进行全文索引,那么我们需要添加一个字段给表

    BbsPost

    psql BBS
    alter table BbsPost add column idxFti tsvector;

    然后,我们要更新这个字段,使之包含合理的分词字段:

    update BbsPost set idxFti = to_tsvector(coalesce(carriecharseg(topic, 1), '') || ' ' || 
    coalesce(carriecharseg(content, 1), ''));

    创建全文索引

    然后,我们需要在这个字段上创建特殊的索引:

    create index bbspost_idxfti_idx on BbsPost using gist(idxFti);

    清理数据库

    之后,我们再清理一下数据库:

    vacuum full analyze analyze;

    这样,全文索引就准备完成了!

    例子二:通过扩展表使用全文索引

    首先,仍然对BBS数据库进行处理,这次我们不直接在原有全文字段表上增加字段,而是另外建立一个表:

    create table bbsPost_fti (id integer, idxfti tsvector);

    我们创建两个字段的表,用于对

    bbsPost

    表进行全文索引。然后,我们给

    bbsPost_fti

    加一个外键:

    alter table bbsPost_fti add foreign key (id) references bbsPost(id);

    这样保证

    bbsPost_fti

    的 id 一定是

    bbsPost

    中存在的。然后,我们可以向

    bbsPost_fti

    里面插入数据:

    insert into bbsPost_fti (id, idxfti) select id, to_tsvector(coalesce(carriecharseg(topic, 1), '') 
    || ' ' || coalesce(carriecharseg(content, 1), '')) from bbsPost order by id;

    这样,初始化所有数据到

    bbsPost_fti

    中。然后创建索引:

    create index idxfti_idx on bbsPost_fti using gist(idxfti);
    create index bbsPost_fti_id_idx on bbsPost_fti (id);

    我们也可以用倒排索引(GIN)进行全文索引,这样检索速度比 GIST 更快:

    create index idxfti_idx on bbsPost_fti using gin(idxfti);

    在我的试验中,GIN比GIST快了将近5倍,唯一的缺点是更新速度明显变慢。这个需要用户自己去平衡了。

    最后,也是清理数据库:

    vacuum full analyze;

    然后我们也可以开始使用了!

    使用全文索引

    我们上面用的是分字程序,那么我们可以这样使用SQL查询来进行数据检索:

    select * from BbsPost where idxFti @@ '中&文';

    这样,就把包含“中”和“文”字的帖子都找了出来。@@ 操作符还支持与或非的逻辑:

    select * from BbsPost where idxFti @@ '(中&文)|(英&文)';

    用圆括弧:() 和 & 和 | 和 ! 可以组合查询条件,对数据表进行查询。目前,我们只拥有分字的程序,将来在完成中文分词程序之后,我们可以实现更简单的搜索,类似:

    select * from BbsPost where idxFti @@ '(中文)|(英文)&(中国)';

    这样的查询,大家可以用 explain analyze 看看,这样的查询的效率和 like 查询的效率差距有多大? ;)

    中文屏蔽词的添加

    在语言里面有很多没有用的屏蔽词,比如英文里面的 "the",那么中文也有许多这样的词,比如“的”字,在这些词上建立索引,没必要也浪费,因此,我们可以用一种叫做 stop word (屏蔽词)的机制,给关闭这些词。具体做法是:

    • 找到$PGHOME/share/contrib/
    • echo "的" | iconv -f gbk -t utf8 > chinese.stop.utf8 (如果你的终端是UTF8的,则可以忽略 iconv 语句。)
    • 更多的 chinese stop word 可以用 echo "屏蔽词" | iconv -f gbk -t utf8 >> chinese.stop.utf8 实现


    至此,使用 tsearch2 进行中文全文索引的最基本的要点已经在此列出,更多相关的信息,我会在有进一步的试验结果之后再详细展开。

    【参考】

    另外一个中文PostgreSQL分词组件参考:http://code.google.com/p/nlpbamboo/wiki/TSearch2

    TSearch官方网站和文档:http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/

    PostgreSQL官方TSearch文档参考:http://www.postgresql.org/docs/8.3/static/textsearch.html
    Using tsearch with PostgreSQL:http://www.michaelhinds.com/tech/pg/tsearch.html

    本文来源:http://www.pgsqldb.org/mwiki/index.php/PostgreSQL%E7%9A%84FTI%E4%B8%8E%E4%B8%AD%E6%96%87%E5%85%A8%E6%96%87%E7%B4%A2%E5%BC%95%E7%9A%84%E5%AE%9E%E8%B7%B5#.E4.B8.AD.E6.96.87.E5.B1.8F.E8.94.BD.E8.AF.8D.E7.9A.84.E6.B7.BB.E5.8A.A0

  • 相关阅读:
    又见Alice and Bob
    算法7-6:图的遍历——广度优先搜索
    算法7-5:图的遍历——深度优先搜索
    水池数目
    过河问题
    括号配对问题
    C# 客户端判断是否安装office03、07或WPS
    C# json
    开源cms
    可执行代码(Executable Code)目标代码(object code)
  • 原文地址:https://www.cnblogs.com/wangbin/p/1437018.html
Copyright © 2011-2022 走看看