技术经验解读:【转】PostgreSQL的FTI(TSearch)与中文全文索引的实践

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 技术经验解读:【转】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 //代码效果参考:http://www.jhylw.com.cn/020723495.html

>= '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分词组件参考:


TSearch官方网站和文档:


PostgreSQL官方TSearch文档参考:


Using


tsearch with PostgreSQL:


本文来源:


aliyun活动

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
1月前
|
存储 关系型数据库 MySQL
PolarDB-X 存储引擎核心技术 | 索引回表优化
数据库系统为了高效地存储、检索和维护数据,采用了多种不同的数据组织结构。不同的组织结构有其特定的用途和优化点,比如提高查询速度、优化写入性能、减少存储空间等,目前 PolarDB-X 采用了 B-Tree 的索引组织结构。
|
1月前
|
关系型数据库 分布式数据库 数据库
【PolarDB开源】PolarDB资源隔离技术:在多租户环境中的应用与优化
【5月更文挑战第29天】PolarDB,阿里云的云原生数据库,在多租户环境中通过逻辑(Schema/Partition隔离)和物理(分布式存储计算节点)隔离保障数据安全和资源独占。它支持动态资源分配,适应不同租户需求,处理大规模并发,提供租户管理及数据访问控制功能。通过优化资源分配算法、提升事务处理能力和强化监控告警,PolarDB确保性能和稳定性,满足多租户的高效数据库服务需求。
69 1
|
1月前
|
监控 关系型数据库 分布式数据库
【PolarDB 开源】PolarDB HTAP 实践:混合事务与分析处理的性能优化策略
【5月更文挑战第21天】PolarDB开源后在HTAP领域表现出色,允许在同一系统处理事务和分析工作负载,提高数据实时性。通过资源分配、数据分区、索引优化等策略提升性能。示例代码展示了创建和查询事务及分析表的基本操作。PolarDB还提供监控工具,帮助企业优化系统并应对业务变化。其HTAP能力为开发者和企业提供了强大支持,推动技术进步,加速数字化时代的业务发展。
396 1
|
19天前
|
存储 算法 数据处理
惊人!PolarDB-X 存储引擎核心技术的索引回表优化如此神奇!
【6月更文挑战第11天】PolarDB-X存储引擎以其索引回表优化技术引领数据库发展,提升数据检索速度,优化磁盘I/O,确保系统在高并发场景下的稳定与快速响应。通过示例代码展示了在查询操作中如何利用该技术高效获取结果。索引回表优化具备出色性能、高度可扩展性和适应性,为应对大数据量和复杂业务提供保障,助力企业与开发者实现更高效的数据处理。
|
1月前
|
存储 关系型数据库 分布式数据库
数据库索引回表困难?揭秘PolarDB存储引擎优化技术
PolarDB分布式版存储引擎采用CSM方案均衡资源开销与可用性。
数据库索引回表困难?揭秘PolarDB存储引擎优化技术
|
1月前
|
Cloud Native 关系型数据库 分布式数据库
【PolarDB开源】PolarDB开源项目未来展望:技术趋势与社区发展方向
【5月更文挑战第29天】PolarDB,阿里云的云原生分布式数据库,正聚焦于云原生、容器化、HTAP与实时分析、智能化运维等技术趋势,旨在提升资源利用率、扩展性及数据分析能力。未来,项目将深化全球开源社区合作,拓宽应用场景,构建开发者生态,以创新技术驱动数据库领域发展,目标成为领先的云数据库服务。
81 1
|
1月前
|
SQL 监控 关系型数据库
【PolarDB开源】PolarDB SQL优化实践:提升查询效率与资源利用
【5月更文挑战第24天】PolarDB是高性能的云原生数据库,强调SQL查询优化以提升性能。本文分享了其SQL优化策略,包括查询分析、索引优化、查询重写、批量操作和并行查询,以及性能监控与调优方法。通过这些措施,可以减少响应时间、提高并发处理能力和降低成本。文中还提供了相关示例代码,展示如何分析查询和创建索引,帮助用户实现更高效的数据库管理。
204 1
|
1月前
|
安全 关系型数据库 分布式数据库
【PolarDB 开源】PolarDB 在金融行业中的实践:高可用与安全合规解决方案
【5月更文挑战第28天】PolarDB,一款适用于金融行业的强大数据库,以其高可用性和安全合规性脱颖而出。通过多副本机制和自动故障转移确保业务连续性,结合严格的访问控制和数据加密技术保护信息安全。在实际应用中,如银行核心系统,PolarDB 负责处理海量交易数据,同时支持主从架构以备故障切换。此外,设置强密码策略和加密存储确保合规性,并通过监控预警及时解决问题。随着金融科技发展,PolarDB 将在云原生架构和人工智能等领域发挥更大作用,助力金融行业创新与进步。
103 0
|
1月前
|
负载均衡 关系型数据库 分布式数据库
【PolarDB开源】PolarDB读写分离实践:优化读取性能与负载均衡策略
【5月更文挑战第26天】PolarDB是云原生关系型数据库,通过读写分离优化性能和扩展性。它设置主节点处理写操作,从节点处理读操作,异步复制保证数据一致性。优化读取性能的策略包括增加从节点数量、使用只读实例和智能分配读请求。负载均衡策略涉及基于权重、连接数和地理位置的分配。实践示例中,电商网站通过主从架构、只读实例和负载均衡策略提升商品查询效率。PolarDB的读写分离与负载均衡为企业应对大数据和高并发提供了有效解决方案。
142 0
|
1月前
|
关系型数据库 分布式数据库 数据库
VLDB顶会论文解读 | PolarDB MySQL高性能强一致集群核心技术详解
在VLDB2023会议上,阿里云瑶池数据库团队的论文介绍了PolarDB-SCC,这是一个创新的云原生数据库系统,确保了低延迟的全局强一致读取。PolarDB-SCC解决了传统主从架构中只读节点可能返回过期数据的问题,实现了在不影响性能的情况下提供强一致性。通过重新设计的主从信息同步机制、线性Lamport时间戳和细粒度修改跟踪,以及利用RDMA优化的日志传输,PolarDB-SCC已经在PolarDB中成功应用超过一年,成为业界首个无感知全局一致性读的云原生数据库解决方案。
67071 0