前言:
PostgreSQL默认分词是按照空格及各种标点符号来分词,但是对于国内更多的是中文文章,按照默认分词方式不符合中文的分词方式。检索了网上很多文章,发现使用最多的是zhparser,并且是开源的,完成能够满足检索需求。
前置:
centOS7
PostgreSQL11
SCWS(下载地址:http://www.xunsearch.com/scws/down/scws-1.2.2.tar.bz2)
zhparser(GitHub地址:https://github.com/amutu/zhparser)
postgresql-devel
zhparser支持PostgreSQL 9.2及以上版本,请确保你的PG版本符合要求。 对于REDHAT/CentOS Linux系统,
请确保安装了相关的库和头文件,一般它们在postgresql-devel软件包中。
- 1
- 2
安装:
1、首先需要安装postgresql-devel
yum install postgresql-devel 或者 yum install postgresql11-devel (我这里使用的第一种,没有指定版本号)
- 1
如果不安装此软件包的话,在编译zhparser文件时会报pg_config: Command not found错误
2、安装SCWS
因为zhparser是基于SCWS(简易中文分词系统)开发的。所以必须首先安装SCWS。
·2.1 创建一个文件夹(这里不详细写创建步骤,这里我统一将SCWS和zhparser放在了/home/pgsql/下面)
·2.2 进入创建的文件夹
·2.3 下载SCWS
wget http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2
- 1
·2.3 安装
[root@pg1 opt]# tar -jxvf scws-1.2.3.tar.bz2
[root@ecs-f49b-0003 pgsql]# cd scws-1.2.3 ; ./configure ; make install
注意:在FreeBSD release 10及以上版本上运行configure时,需要增加--with-pic选项。
如果是从github上下载的scws源码需要先运行以下命令生成configure文件:
touch README;aclocal;autoconf;autoheader;libtoolize;automake --add-missing
- 1
- 2
- 3
- 4
- 5
- 6
3、安装zhparser
·3.1 这里可以参考GitHub上的步骤,https://github.com/amutu/zhparser ,忽略创建extension步骤,这里需要按照下面步骤检查和创建extension ,与GitHub上写的略有不同(建议编译和安装zhparser时采用指定PG_CONFIG 的方式指定版本编译扩展,避免直接使用make && make install 编译结果与安装的数据库版本不一致)。
ps:因为在安装PostgreSQL时,我是用YUM方式安装的,clang并没有被加入,所以在指定 PG_CONFIG
版本编译扩展时可能会报--新增插件异常(clang: Command not found),或者直接编译不通过。
这里可以参考(https://yq.aliyun.com/articles/698119 ),关闭PostgreSQL新增插件的bc编译项来解决:
vim /usr/pgsql-11/lib/pgxs/src/makefiles/pgxs.mk
注释所有的with_llvm相关
- 1
- 2
- 3
- 4
- 5
- 6
下面是我编译后的结果,编译的文件没有直接放到pgsql中,下面图片中标注中的是最后编辑后文件的放置路径。(如果未出现相同的问题,那么就可以跳过文件整理步骤直接去创建extension。)
因为编译后的文件不在应该在的文件夹中,这里就需要手动去将文件移动至指定目录:
/usr/lib64/pgsql/zhparser.so → /usr/pgsql-11/lib (/usr/pgsql-11/是PostgreSQL安装后路径,如果PostgreSQL是使用的YUM安装方式,路径应该是一样)
/usr/share/pgsql/extension/ → /usr/pgsql-11/share/extension
/usr/share/pgsql/tsearch_data/ → /usr/pgsql-11/share/tsearch_data
·3.2 创建extension
整理好这些文件后,就可以继续创建extension了。
·3.2.1 切换到postgres账户
su - postgres
- 1
·3.2.2 安装扩展(每新建一个数据库都需要执行这一步;这里我直接使用的已有的一个测试数据库)
#查询已有的解析器
knowledge=# dFp
- 1
- 2
这里可以看到就只有一个默认的解析器。下面开始创建扩展:
knowledge=# create extension zhparser;
- 1
这里再来看一次已有的解析器:
这样解析器就添加到当前数据库了。但是此时还是不能用,还需要创建使用zhparser作为解析器的全文搜索的配置,也就是需要给zhparser解析器取一个在sql里面可以使用的名字。这里测试取的是(‘zh’)。
再继续向全文搜索配置中增加token映射:
knowledge=# ALTER TEXT SEARCH CONFIGURATION zh ADD MAPPING FOR n,v,a,i,e,l,j WITH simple;
- 1
这样就算是安装配置完成了。下面来测试一下效果:
追加问题解决
最近使用中遇到一个查询时的问题:
例如一篇文章中有“…合江县…”,分词分出来的就是合江县,如果查询的时候输入的是合江,那么是匹配不到的,所以这里需要在继续设置一下全文检索的复合等级;在命令行中使用上一节中介绍的 scws 命令测试分词配置,如我认为复合等级为 7 时分词结果最好,则我在 postgresql.conf添加配置:
zhparser.multi_short = true #短词复合: 1
zhparser.multi_duality = true #散字二元复合: 2
zhparser.multi_zmain = true #重要单字复合: 4
zhparser.multi_zall = false #全部单字复合: 8
- 1
- 2
- 3
- 4
然后重启数据库,将之前的分词重新全部生成一遍,在来查询,问题解决。
OK!!!这样就可以满足当前需求了,完工!!!
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 中分词一直报错,读取词库文件失败。我经过多次实验,总结出了一套制作一个词典文件的方法:
- 准备词库源文件 mydict.txt:词库文件的内容每一行的格式为
词 TF IDF 词性
,词是必须的,而TF 词频(Term Frequency)、IDF 反文档频率(Inverse Document Frequency) 和 词性
都是可选的,除非确定自己的词典资料是对的且符合 scws 的配置,不然最好还是留空,让 scws 自已确定; - 在
postgresql.conf
中设置zhparser.extra_dicts = "mydict.txt"
同时设置zhparser.dict_in_memory = true
; - 命令行进入 PgSQL,执行一条分词语句
select to_tsquery('parser', '随便一个词')
,分词会极慢,请耐心(请保证此时只有一个分词语句在执行); - 分词成功后,在/tmp/目录下找到生成的
scws-xxxx.xdb
替换掉share/tsearch_data/dict.utf8.xdb
; - 删除刚加入的
extra_dicts dict_in_memory
配置,重启服务器。
扩展
由于查询的是 POI 的名称,一般较短,且很多词并无语义,又考虑到用户的输入习惯,一般会输入 POI 名称的前几个字符,而且 scws 的分词准确率也不能达到100%,于是我添加了名称的前缀查询来提高查询的准确率,即使用 B树索引 实现 LIKE '关键词%'
的查询。这里需
这里要注意的是,创建索引时要根据字段类型配置 操作符类
,不然索引可能会不生效,如在 字段类型为 varchar 的字段上创建索引需要使用语句CREATE INDEX idx_name ON table(COLUMN varchar_pattern_ops)
,这里的 varcharpatternops 就是操作符类,操作符类的介绍和选择可以查看文档:11.9. 操作符类和操作符族。
自此,一个良好的全文检索系统就完成了。
总结
简单的数据迁移并不是终点,后续要做的还有很多,如整个系统的数据同步、查询效率优化、查询功能优化(添加拼音搜索、模糊搜索)等。特别是查询效率,不知道是不是我配置有问题,完全达不到那种 E级毫秒 的速度,1kw 的数据效率在进行大结果返回时就大幅下降(200ms),只好老老实实地提前进行了分表,目前百万级查询速度在 20ms 以内,优化还有一段路要走。
不过这次倒是对 技术的“生态”有了个更深的体会,这方面 PgSQL 确实和 MySQL 差远了,使用 MySQL 时再奇葩的问题都能在网上快速找到答案,而 PgSQL 就尴尬了,入门级的问题搜索 stackoverflow 来来回回就那么几个对不上的回答。虽然也有阿里的“德哥”一样的大神在辛苦布道,但用户的数量才是根本。不过,随着 PgSQL 越来越完善,使用它的人一定会越来越多的,我这篇文章也算是为 PgSQL 加温了吧,哈哈~希望能帮到后来的使用者。