如何使用PostgreSQL进行中文全文检索


Posted in PostgreSQL onMay 27, 2021

开始

安装

首先是安装 PgSQL,这里我使用的是 PgSQL 9.6,PgSQL 10 也刚发布了,有兴趣的可以尝下鲜。

PgSQL 的安装可以说非常复杂了,除了要安装 Server 和 Client 外,还需要安装 devel 包。为了实现空间索引功能,我们还要安装最重要的 PostGIS 插件,此插件需要很多依赖,自己手动安装非常复杂而且很可能出错。

推荐自动化方式安装,Yum 一定要配合 epel 这样的 Yum 源,保障能将依赖一网打尽。当然最好的还是使用 docker 来运行,找个镜像就行了。

插件

由于 PgSQL 的很多功能都由插件实现,所以还要安装一些常用的插件,如:

postgis_topology(管理面、边、点等拓扑对象)

pgrouting(路径规划)

postgis_sfcgal(实现3D相关算法)

fuzzystrmatch(字符串相似度计算)

address_standardizer/address_standardizer_data_us(地址标准化)

pg_trgm(分词索引)

这些插件在安装目录 /path/extensions 下编译完毕后,在数据库中使用前要先使用 create extension xxx 启用。

启动

1.切换到非 root 用户。(PgSQL 在安装完毕后会创建一个名为 postgres 的超级用户,我们可以使用这个超级用户来操作 PgSQL,后期建议重新创建一个普通用户用来管理数据);

2.切换到 /installPath/bin/ 目录下,PgSQL 在此目录下提供了很多命令,如 createdb、createuser、dropdb、pg_dump 等;

3.使用 createdb 命令初始化一个文件夹 dir_db (此目录不能已存在)存放数据库物理数据,使用 -E UTF8 参数指定数据库字符集为 utf-8;

4.使用 pg_ctl -D dir_db 指定数据库启动后台服务;

5.使用 psql -d db 在命令行登陆 PgSQL;

配置

安装完毕后还要配置一些比较基本的参数才能正常使用。

Host权限

PgSQL需要在 pg_hba.conf 文件中配置数据库 Host 权限,才能被其他机器访问。

# TYPE  DATABASE        USER            ADDRESS                 METHOD

local   all             all                                     trust

host    all             all             127.0.0.1/32            md5

host    all             all             172.16.0.1/16            md5

文件中注释部分对这几个字段介绍得比较详细, 我们很可能需要添加 host(IP) 访问项, ADDRESS 是普通的网段表示法,METHOD 推荐使用 md5,表示使用 md5 加密传输密码。

服务器配置

服务器配置在 postgresql.conf中,修改配置后需要 使用 pg_ctl restart -D dir_db 命令重启数据库;

此外,我们也可以在登陆数据库后修改配置项:使用 SELECT * FROM pg_settings WHERE name = 'config'; 查询当前配置项,再使用 UPDATE 语句更新配置。但有些配置如内存分配策略是只在当前 session 生效的,全局生效需要在配置文件中修改,再重启服务器。

我们可以修改配置并用客户端验证 SQL 语句的优化,使用 \timing on 开启查询计时,使用 EXPLAIN ANALYSE 语句 分析查询语句效率。 下面介绍两个已实践过的配置参数:

  • shared_buffers:用于指定共享内存缓冲区所占用的内存量。它应该足够大来存储常使用的查询结果,以减少物理I/O。但它也不能太大,以避免系统 内存swap 的发生, 一般设置为系统内存的 20%。
  • work_mem:一个连接的工作内存,在查询结果数据量较大时,此值如果较小的话,会导致大量系统 I/O,导致查询速度急剧下降,如果你的 explain 语句内 buffer 部分 read数值过大,则表示工作内存不足,需要调整加此参数。但此值也不能太大,需要保证 work_mem * max_connections + shared_buffers + 系统内存 < RAM,不然同样可能会导致系统 内存swap。

这样,PgSQL 就能作为一个正常的关系型数据使用了。

分词

全文索引的实现要靠 PgSQL 的 gin 索引。分词功能 PgSQL 内置了英文、西班牙文等,但中文分词需要借助开源插件 zhparser;

SCWS

要使用 zhparser,我们首先要安装 SCWS 分词库,SCWS 是 Simple Chinese Word Segmentation 的首字母缩写(即:简易中文分词系统),其 GitHub 项目地址为 hightman-scws,我们下载之后可以直接安装。

安装完后,就可以在命令行中使用 scws 命令进行测试分词了, 其参数主要有:

  • -c utf8 指定字符集
  • -d dict 指定字典 可以是 xdb 或 txt 格式
  • -M 复合分词的级别, 1~15,按位异或的 1|2|4|8 依次表示 短词|二元|主要字|全部字,默认不复合分词,这个参数可以帮助调整到最想要的分词效果。

zhpaser

1.下载 zhparser 源码 git clone https:github.com/amutu/zhparser.git;

2.安装前需要先配置环境变量:export PATH=$PATH:/path/to/pgsql;

3.make && make install编译 zhparser;

4.登陆 PgSQL 使用 CREATE EXTENSION zhparser; 启用插件;

5.添加分词配置

CREATE TEXT SEARCH CONFIGURATION parser_name (PARSER = zhparser); // 添加配置
ALTER TEXT SEARCH CONFIGURATION parser_name ADD MAPPING FOR n,v,a,i,e,l,j WITH simple; // 设置分词规则 (n 名词 v 动词等,详情阅读下面的文档)

6.给某一列的分词结果添加 gin 索引 create index idx_name on table using gin(to_tsvector('parser_name', field));

7.在命令行中使用上一节中介绍的 scws 命令测试分词配置,如我认为复合等级为 7 时分词结果最好,则我在 postgresql.conf添加配置

zhparser.multi_short = true #短词复合: 1

zhparser.multi_duality = true  #散字二元复合: 2

zhparser.multi_zmain = true  #重要单字复合: 4

zhparser.multi_zall = false  #全部单字复合: 8

SQL

查询中我们可以使用最简单的 SELECT * FROM table WHERE to_tsvector('parser_name', field) @@ 'word' 来查询 field 字段分词中带有 word 一词的数据;

使用 to_tsquery() 方法将句子解析成各个词的组合向量,如 国家大剧院 的返回结果为 '国家' & '大剧院' & '大剧' & '剧院' ,当然我们也可以使用 & | 符号拼接自己需要的向量;在查询 长句 时,可以使用 SELECT * FROM table WHERE to_tsvector('parser_name', field) @@ to_tsquery('parser_name','words');

有时候我们想像 MySQL 的 SQL_CALC_FOUND_ROWS 语句一样同步返回结果条数,则可以使用 SELECT COUNT(*) OVER() AS score FROM table WHERE ...,PgSQL 会在每一行数据添加 score 字段存储查询到的总结果条数;

到这里,普通的全文检索需求已经实现了。

优化

我们接着对分词效果和效率进行优化:

存储分词结果

我们可以使用一个字段来存储分词向量,并在此字段上创建索引来更优地使用分词索引:

ALTER TABLE table ADD COLUMN tsv_column tsvector;           // 添加一个分词字段
UPDATE table SET tsv_column = to_tsvector('parser_name', coalesce(field,''));   // 将字段的分词向量更新到新字段中
CREATE INDEX idx_gin_zhcn ON table USING GIN(tsv_column);   // 在新字段上创建索引
CREATE TRIGGER trigger_name BEFORE INSERT OR UPDATE  ON table FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(tsv_column, 'parser_name', field); // 创建一个更新分词触发器

这样,再进行查询时就可以直接使用 SELECT * FROM table WHERE tsv_column @@ 'keyword' 了。

这里需要注意,这时候在往表内插入数据的时候,可能会报错,提示指定 parser_name 的 schema, 这时候可以使用 \dF 命令查看所有 text search configuration 的参数:

               List of text search configurations

   Schema   |    Name    |              Description

------------+------------+---------------------------------------

 pg_catalog | english    | configuration for english language

 public     | myparser   |

注意 schema 参数,在创建 trigger 时需要指定 schema, 如上面,就需要使用 public.myparser。

添加自定义词典

我们可以在网上下载 xdb 格式的词库来替代默认词典,词库放在 share/tsearch_data/ 文件夹下才能被 PgSQL 读取到,默认使用的词库是 dict.utf8.xdb。要使用自定义词库,可以将词库放在词库文件夹后,在 postgresql.conf 配置 zhparser.extra_dict="mydict.xdb" 参数;

当我们只有 txt 的词库,想把这个词库作为默认词库该怎么办呢?使用 scws 带的scwe-gen-dict 工具或网上找的脚本生成 xdb 后放入词库文件夹后,在 PgSQL 中分词一直报错,读取词库文件失败。我经过多次实验,总结出了一套制作一个词典文件的方法:

1.准备词库源文件 mydict.txt:词库文件的内容每一行的格式为词 TF IDF 词性,词是必须的,而 TF 词频(Term Frequency)、IDF 反文档频率(Inverse Document Frequency) 和 词性 都是可选的,除非确定自己的词典资料是对的且符合 scws 的配置,不然最好还是留空,让 scws 自已确定;

2.在 postgresql.conf 中设置 zhparser.extra_dicts = "mydict.txt" 同时设置 zhparser.dict_in_memory = true;

3.命令行进入 PgSQL,执行一条分词语句 select to_tsquery('parser', '随便一个词') ,分词会极慢,请耐心(请保证此时只有一个分词语句在执行);

4.分词成功后,在/tmp/目录下找到生成的 scws-xxxx.xdb 替换掉 share/tsearch_data/dict.utf8.xdb;

5.删除刚加入的 extra_dicts dict_in_memory 配置,重启服务器。

扩展

由于查询的是 POI 的名称,一般较短,且很多词并无语义,又考虑到用户的输入习惯,一般会输入 POI 名称的前几个字符,而且 scws 的分词准确率也不能达到100%,于是我添加了名称的前缀查询来提高查询的准确率,即使用 B树索引 实现 LIKE '关键词%' 的查询。这里需

这里要注意的是,创建索引时要根据字段类型配置 操作符类,不然索引可能会不生效,如在 字段类型为 varchar 的字段上创建索引需要使用语句CREATE INDEX idx_name ON table(COLUMN varchar_pattern_ops),这里的 varcharpatternops 就是操作符类。

自此,一个良好的全文检索系统就完成了。

总结

简单的数据迁移并不是终点,后续要做的还有很多,如整个系统的数据同步、查询效率优化、查询功能优化(添加拼音搜索、模糊搜索)等。特别是查询效率,不知道是不是我配置有问题,完全达不到那种 E级毫秒 的速度,1kw 的数据效率在进行大结果返回时就大幅下降(200ms),只好老老实实地提前进行了分表,目前百万级查询速度在 20ms 以内,优化还有一段路要走。

不过这次倒是对 技术的“生态”有了个更深的体会,这方面 PgSQL 确实和 MySQL 差远了,使用 MySQL 时再奇葩的问题都能在网上快速找到答案,而 PgSQL 就尴尬了,入门级的问题搜索 stackoverflow 来来回回就那么几个对不上的回答。虽然也有阿里的“德哥”一样的大神在辛苦布道,但用户的数量才是根本。不过,随着 PgSQL 越来越完善,使用它的人一定会越来越多的,我这篇文章也算是为 PgSQL 加温了吧,哈哈~希望能帮到后来的使用者。

以上就是如何使用PostgreSQL进行中文全文检索的详细内容,更多关于使用PostgreSQL进行中文全文检索的资料请关注三水点靠木其它相关文章!

PostgreSQL 相关文章推荐
通过Qt连接OpenGauss数据库的详细教程
Jun 23 PostgreSQL
浅谈PostgreSQL表分区的三种方式
Jun 29 PostgreSQL
关于PostgreSQL JSONB的匹配和交集问题
Sep 14 PostgreSQL
PostgreSQL13基于流复制搭建后备服务器的方法
Jan 18 PostgreSQL
PostgreSQL并行计算算法及参数强制并行度设置方法
Apr 06 PostgreSQL
PostgreSQL并行计算算法及参数强制并行度设置方法
Apr 07 PostgreSQL
PostgreSQL数据库去除重复数据和运算符的基本查询操作
Apr 12 PostgreSQL
postgreSQL数据库基础知识介绍
Apr 12 PostgreSQL
PostgreSQL基于pgrouting的路径规划处理方法
Apr 18 PostgreSQL
PostgreSQL怎么创建分区表详解
Jun 25 PostgreSQL
PostgreSQL存储过程实用脚本(二):创建函数入门
PostgreSQL将数据加载到buffer cache中操作方法
Apr 16 #PostgreSQL
自定义函数实现单词排序并运用于PostgreSQL(实现代码)
Centos环境下Postgresql 安装配置及环境变量配置技巧
PostgreSQL通过oracle_fdw访问Oracle数据的实现步骤
postgresql无序uuid性能测试及对数据库的影响
Jun 11 #PostgreSQL
postgres之jsonb属性的使用操作
Jun 23 #PostgreSQL
You might like
基于PHP的cURL快速入门教程 (小偷采集程序)
2011/06/02 PHP
PHP设置图片文件上传大小的具体实现方法
2013/10/11 PHP
PHP获取中国时间(上海时区时间)及美国时间的方法
2017/02/23 PHP
php中的explode()函数实例介绍
2019/01/18 PHP
动态加载图片路径 保持JavaScript控件的相对独立性
2010/09/03 Javascript
一个背景云变换js特效 鼠标移动背景云变化
2012/12/28 Javascript
JS代码同步文本框内容的实例方法
2013/07/12 Javascript
JavaScript将Web页面内容导出到Word及Excel的方法
2015/02/13 Javascript
JS根据key值获取URL中的参数值及把URL的参数转换成json对象
2015/08/26 Javascript
基于JavaScript实现 网页切出 网站title变化代码
2016/04/03 Javascript
微信小程序自定义弹窗滚动与页面滚动冲突的解决方法
2019/07/16 Javascript
小程序Scroll-view上拉滚动刷新数据
2020/06/21 Javascript
vue中实现点击变成全屏的多种方法
2020/09/27 Javascript
[57:24]LGD vs VGJ.T 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
用Python编写web API的教程
2015/04/30 Python
Python实现高效求解素数代码实例
2015/06/30 Python
利用python微信库itchat实现微信自动回复功能
2017/05/18 Python
pandas数据预处理之dataframe的groupby操作方法
2018/04/13 Python
Python错误处理操作示例
2018/07/18 Python
pycharm: 恢复(reset) 误删文件的方法
2018/10/22 Python
pandas去除重复列的实现方法
2019/01/29 Python
Python3实现汉语转换为汉语拼音
2019/07/08 Python
django-crontab 定时执行任务方法的实现
2019/09/06 Python
python继承threading.Thread实现有返回值的子类实例
2020/05/02 Python
利用python下载scihub成文献为PDF操作
2020/07/09 Python
Python使用jpype模块调用jar包过程解析
2020/07/29 Python
财务会计专业毕业生自荐信
2013/10/19 职场文书
酒店副总经理岗位职责范本
2014/02/04 职场文书
记帐员岗位责任制
2014/02/08 职场文书
中职三好学生事迹材料
2014/08/24 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
未婚证明书模板
2014/10/08 职场文书
2014年大学教师工作总结
2014/12/02 职场文书
2015年幼师个人工作总结
2015/10/15 职场文书
浅谈Redis缓冲区机制
2022/06/05 Redis
nginx静态资源的服务器配置方法
2022/07/07 Servers