MacOS 系统上 Posgresql 的中文全文搜索配置和使用

最近在做一个小方案,体验一下 PG 的全文搜索。由于我的工作环境是 MacOS, 所以记录一下,等搞定了才发现这个跟 Linux 没多大区别。

安装 postgresql

brew install postgresql

pg_jieba 方案

安装

brew install cmake
mkdir ~/tmp && cd ~/tmp && git clone https://github.com/jaiminpan/pg_jieba && cd pg_jieba
git submodule update --init --recursive
mkdir build && cd build
cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/postgres ..
make install

测试

$ psql -d vapordb
psql (12.2)
Type "help" for help.

@vapordb=# CREATE EXTENSION pg_jieba;
CREATE EXTENSION
@vapordb=# SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                   to_tsvector
----------------------------------------------------------------------------------
 '中国科学院':5 '小明':1 '日本京都大学':10 '毕业':3 '深造':11 '硕士':2 '计算所':6
(1 row)

@vapordb=# \quit

在测试时,可以感觉到 jieba 的第一次分词有明显的延迟和卡顿,可以通过 Postgresq 预加载 jieba 的动态库和配置文件改善(/usr/local/var/postgres/postgresql.conf)。

#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------

# Add settings for extensions here
# pg_jieba
shared_preload_libraries = 'pg_jieba.so'  # (change requires restart)
# default_text_search_config='pg_catalog.simple'; default value
default_text_search_config='jiebacfg'; uncomment to make 'jiebacfg' as default

zhparser 方案

安装 scws

brew install scws
scws -v

下载词典文件

mkdir -p /usr/local/etc/scws
curl "http://www.xunsearch.com/scws/down/scws-dict-chs-utf8.tar.bz2" | tar xvjf -
mv dict.utf8.xdb /usr/local/etc/scws/

测试效果

scws -c utf8 -d /usr/local/etc/scws/dict.utf8.xdb -r /usr/local/opt/scws/etc/rules.utf8.ini -M 9 "PostgreSQL 自带有一个简易的全文检索引擎"
PostgreSQL 自带 自 带 有 一个 一 个 简易 简 易 的 全文检索 全文 检索 全 文 检 索 引擎 引 擎
+--[scws(scws-cli/1.2.3)]----------+
| TextLen:   52                  |
| Prepare:   0.0007    (sec)     |
| Segment:   0.0002    (sec)     |
+--------------------------------+

安装 zhparser

mkdir ~/tmp && cd ~/tmp
git clone https://github.com/amutu/zhparser.git && cd zhparser
make install

测试 zhparser

$ psql -d vapordb
psql (12.2)
Type "help" for help.

@vapordb=# CREATE EXTENSION zhparser;
CREATE EXTENSION
@vapordb=# CREATE TEXT SEARCH CONFIGURATION zhcnsearch (PARSER = zhparser);
CREATE TEXT SEARCH CONFIGURATION
@vapordb=# ALTER TEXT SEARCH CONFIGURATION zhcnsearch ADD MAPPING FOR n,v,a,i,e,l,j WITH simple;
ALTER TEXT SEARCH CONFIGURATION
@vapordb=# SELECT to_tsvector('zhcnsearch', '人生苦短,我用 Python');
               to_tsvector
------------------------------------------
 'python':5 '人生':1 '用':4 '短':3 '苦':2
(1 row)

@vapordb=# \quit

大功告成。

对比

两种方案效果上差不多.

$ psql -d vapordb
psql (12.2)
Type "help" for help.

@vapordb=# SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                   to_tsvector
----------------------------------------------------------------------------------
 '中国科学院':5 '小明':1 '日本京都大学':10 '毕业':3 '深造':11 '硕士':2 '计算所':6
(1 row)

@vapordb=# SELECT * FROM to_tsvector('zhcnsearch', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                to_tsvector
---------------------------------------------------------------------------
 '中国科学院计算所':4 '小明':1 '日本京都大学':5 '毕业':3 '深造':6 '硕士':2
(1 row)

@vapordb=# \quit

如何使用

对于全文检索,有两种使用方式,大家可以权衡自己的内容进行选择。

  1. 在搜索的时候进行分词,然后搜索对应的字段。
  2. 提前把表中需要检索的字段进行分词,保存到一个新的字段中,再在这个字段上建立索引进行搜。

两种方案就是时间和空间的取舍:第一种方式创建索引简单,存储空间少,但是比较慢。第二种方案由于预先进行了分词并存储,浪费了空间,但是时间上肯定用得少。创建索引也有两种方案:gin 索引和 rum 索引。

第一种

创建索引:

CREATE INDEX idx_xxxx ON xxxx_table USING gin(to_tsvector('jiebacfg',
COALESCE(xx_field, '') || COALESCE(xxx_field, '')));

查询:

EXPLAIN ANALYSE SELECT * FROM xxxx_table
        WHERE to_tsvector('jiebacfg', COALESCE(xx_field, '') || COALESCE(xxx_field, '')) @@
        to_tsquery('jiebacfg', '关键字或者句子');

第二种

创建 tsv 字段和索引

ALTER TABLE xxxx_table ADD COLUMN tsv tsvector;
UPDATE xxxx_table SET tsv_field = to_tsvector('jiebacfg', COALESCE(xx_field, '') || COALESCE(xxx_field, ''));
CREATE INDEX idx_xxxx ON xxxx_table USING gin(tsv_field);

查询:

EXPLAIN ANALYSE SELECT * FROM xxxx_table WHERE tsv_field @@ to_tsquery('jiebacfg', '关键词或者句子');

当然因为是预先分词保存,所以需要在 update 的时候藉由 触发器 来更新 tsv 字段,。

CREATE TRIGGER tsvector_update BEFORE INSERT OR UPDATE
       ON xxxx_table FOR EACH ROW  EXECUTE PROCEDURE tsvector_update_trigger('tsv_field', 'jiebacfg', 'xx_field', 'xxx_field');

rum 索引

使用 rum 索引类似, 但是 rum 引擎默认是没有安装的,需要自己编译,暂时先不用了。

CREATE INDEX idx_xxxx ON xxxx_table USING rum(tsv_field rum_tsvector_ops);

另外 rum 还支持相似度的查询:

SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
SELECT * FROM rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算所'));