使用PostgreSQL进行全文检索

* { color: #3e3e3e }
body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif; font-size: 15px }
p { line-height: 25.6px; text-align: justify; margin: 23.7px 0 }
blockquote { border-left: 2px solid rgba(128,128,128,0.075); background-color: rgba(128,128,128,0.05); padding: 10px -1px; margin: 0px }
blockquote p { color: #898989; margin: 0px }
strong { font-weight: 700; color: #3e3e3e }
pre { background-color: #f8f8f8; overflow: scroll; padding: 12px 13px; font-size: 13px; color: #898989 }
h1,h2,h3,h4,h5,h6 { text-align: left; font-weight: bold }
hr { border: 1px solid #ddd }
h1 { font-size: 170% }
h1:first-child { margin-top: 0; padding-top: .25em; border-top: none }
table { padding: 0; border-collapse: collapse }
table tr { border-top: 1px solid #cccccc; background-color: white; margin: 0; padding: 0 }
table tr:nth-child(2n) { background-color: #f8f8f8 }
table tr th { font-weight: bold; border: 1px solid #cccccc; margin: 0; padding: 6px 13px }
table tr td { border: 1px solid #cccccc; margin: 0; padding: 6px 13px }
table tr th :first-child,table tr td :first-child { margin-top: 0 }
table tr th :last-child,table tr td :last-child { margin-bottom: 0 }
a { color: #4183C4; text-decoration: none }
ul,ol { padding-left: 30px }
li { line-height: 24px }
hr { height: 2px; padding: 0; margin: 16px 0; background-color: #e7e7e7; border: 0 none; overflow: hidden; border-bottom: 1px solid #ddd }
pre { background: #f2f2f2; padding: 12px 13px }
code { padding: 2px 4px; color: #c7254e; background: #f9f2f4 }
code[class*="language-"],pre[class*="language-"] { color: black; background: none; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; text-align: left; white-space: pre; word-spacing: normal; line-height: 1.5 }
pre[class*="language-"] { position: relative; margin: .5em 0; border-left: 10px solid #358ccb; background-color: #fdfdfd; background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); overflow: visible; padding: 0 }
code[class*="language"] { max-height: inherit; height: 100%; padding: 0 1em; display: block; overflow: auto }
:not(pre)>code[class*="language-"],pre[class*="language-"] { background-color: #fdfdfd; margin-bottom: 1em }
:not(pre)>code[class*="language-"] { position: relative; padding: .2em; color: #c92c2c; border: 1px solid rgba(0, 0, 0, 0.1); display: inline; white-space: normal }
pre[class*="language-"]::before,pre[class*="language-"]::after { content: ""; z-index: -2; display: block; position: absolute; bottom: 0.75em; left: 0.18em; width: 40%; height: 20%; max-height: 13em }
:not(pre)>code[class*="language-"]::after,pre[class*="language-"]::after { right: 0.75em; left: auto }
.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata { color: #7D8B99 }
.token.punctuation { color: #5F6364 }
.token.property,.token.tag,.token.boolean,.token.number,.token.function-name,.token.constant,.token.symbol,.token.deleted { color: #c92c2c }
.token.selector,.token.attr-name,.token.string,.token.char,.token.function,.token.builtin,.token.inserted { color: #2f9c0a }
.token.operator,.token.entity,.token.url,.token.variable { color: #a67f59; background: rgba(255, 255, 255, 0.5) }
.token.atrule,.token.attr-value,.token.keyword,.token.class-name { color: #1990b8 }
.token.regex,.token.important { color: #e90 }
.language-css .token.string,.style .token.string { color: #a67f59; background: rgba(255, 255, 255, 0.5) }
.token.important { font-weight: normal }
.token.bold { font-weight: bold }
.token.italic { font-style: italic }
.token.entity { cursor: help }
.namespace { opacity: .7 }
.token.tab:not(:empty)::before,.token.cr::before,.token.lf::before { color: #e0d7d1 }
pre[class*="language-"].line-numbers { padding-left: 0 }
pre[class*="language-"].line-numbers code { padding-left: 3.8em }
pre[class*="language-"].line-numbers .line-numbers-rows { left: 0 }
pre[class*="language-"][data-line] { padding-top: 0; padding-bottom: 0; padding-left: 0 }
pre[data-line] code { position: relative; padding-left: 4em }
pre .line-highlight { margin-top: 0 }
pre.line-numbers { position: relative; padding-left: 3.8em; counter-reset: linenumber }
pre.line-numbers>code { position: relative }
.line-numbers .line-numbers-rows { position: absolute; top: 0; font-size: 100%; left: -3.8em; width: 3em; letter-spacing: -1px; border-right: 1px solid #999 }
.line-numbers-rows>span { display: block; counter-increment: linenumber }
.line-numbers-rows>span::before { content: counter(linenumber); color: #999; display: block; padding-right: 0.8em; text-align: right }

前言

PostgreSQL 被称为是“最高级的开源数据库”,它的数据类型非常丰富,用它来解决一些比较偏门的需求非常适合。

前些天将 POI 点关键词查询的功能迁到了 PgSQL,总算对前文 空间索引 - 各数据库空间索引使用报告 有了一个交代。

由于 PgSQL 国内的资料较少,迁移过程踩了不少坑,这里总结记录一下,帮助后来的同学能顺利使用 PgSQL。而且目前在灰度测试刚布了一台机器,后续可能还要添加机器,整理一下流程总是好的。

文章经常被人爬,而且还不注明原地址,我在这里的更新和纠错没法同步,这里注明一下原文地址:http://www.cnblogs.com/zhenbianshu/p/7795247.html


开始

安装

首先是安装 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 就是操作符类,操作符类的介绍和选择可以查看文档:11.9. 操作符类和操作符族

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


总结

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

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

关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我,博客一直在更新,欢迎 关注

参考:

PostgreSQL系统配置优化

[PG]使用 zhparser 进行中文分词全文检索

SCWS 中文分词

Fast Search Using PostgreSQL Trigram Indexes

使用阿里云PostgreSQL zhparser时不可不知的几个参数

德哥的PostgreSQL私房菜 - 史上最屌PG资料合集

时间: 2024-10-11 16:32:58

使用PostgreSQL进行全文检索的相关文章

PostgreSQL 全文检索

PostgreSQL 8.3.1  全文检索(转) 在postgreSQL 8.3自带支持全文检索功能,在之前的版本中需要安装配置tsearch2才能使用,安转配置tsearch2就不再多说了,主要介绍一下8.3中自带全文检索功能. 全文检索类型(Text Search Types) postgreSQL设计支持全文检索,提供两个数据类型(tsvector,tsquery),并且通过动态检索自然语言文档的集合,定位到最匹配的查询结果. tsvector 一个tsvector的值是唯一分词的分类列

某电商平台项目开发记要——全文检索(转)

开发Web应用时,你经常要加上搜索功能.甚至还不知能要搜什么,就在草图上画了一个放大镜. 说到目前计算机的文字搜索在应用上的实现,象形文字天生就比拼音字母劣势的多,分词.词性判断.拼音文字转换啥的,容易让人香菇. 首先我们来了解下什么是Inverted index,翻译过来的名字有很多,比如反转索引.倒排索引什么的,让人不明所以,可以理解为:一个未经处理的数据库中,一般是以文档ID作为索引,以文档内容作为记录.而Inverted index 指的是将单词或记录作为索引,将文档ID作为记录,这样便

大数据工具集

类别 名称 官网 备注 查询引擎 Phoenix http://phoenix.incubator.apache.org/ Salesforce公司出品,Apache HBase之上的一个SQL中间层,完全使用Java编写 Stinger http://hortonworks.com/labs/stinger/ 原叫Tez,下一代Hive,Hortonworks主导开发,运行在YARN上的DAG计算框架 http://tez.incubator.apache.org/ Presto http:/

开源大数据利器汇总

类别 名称 官网 备注 查询引擎 Phoenix http://phoenix.incubator.apache.org/ Salesforce公司出品,Apache HBase之上的一个SQL中间层,完全使用Java编写 Stinger http://hortonworks.com/labs/stinger/ http://tez.incubator.apache.org/ 原叫Tez,下一代Hive,Hortonworks主导开发,运行在YARN上的DAG计算框架 Presto http:/

利用PostgreSQL实现毫秒级全文检索

Lateral是一家内容推荐服务提供商,其模拟程序使用PostgreSQL存储文档.每个文档包含一个 text列和一个存储标题.日期和URL等元数据的JSON列.他们希望为模拟程序创建快速搜索功能,搜索文档全文和标题,生成推荐内容.近 日,Lateral首席技术官Max撰文介绍了他们的做法. 为了实现这一目标,可以选择开源解决方案Apache Solr或Elasticsearch,也可以选择托管解决方案Elastic或Algolia,但出于以下考虑,他们选择了PostgreSQL的全文搜索功能:

自己开发网站全文检索系统

本文是我写的一篇数据库相关的作业Report,在这里贴出来 1. 概述 1.1. 问题提出 假如你拥有一个庞大的网站,内容又多,那么来访者往往很难找到自己所需要的东东,这时候你就需要一个站内搜索来帮助来访者更快的找到索要的资料了! 1.2. 解决的办法 搭建自己的全文检索系统.1.2.1. 什么是全文检索全文检索是一种将文件中所有文本与检索项匹配的文字资料检索方法.全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统.目前最大的搜索引擎Google和Baidu使用的就是全文检索

[翻译]PostgreSQL比MySQL/MariaDB的优势

近十年来很多开发者和专业人士对MySQL和PostgreSQL进行了比较,大部分人认为后者性能更优.PostgreSQL的支持者认为它的标准支持和ACID[1]性都超过了MySQL.MySQL还流行的原因在于还有很多Linux的Web程序安装包里带着它,但是自从拥有MySQL版权和商标的Sun被Oracle收购后,人们都在担心MySQL不会像以前那样开放了.同时PostgreSQL不仅快,而且支持JSON,使他成为少数的支持NoSQL的关系型数据库.(小道消息:MySQL的作者Monty Wid

sphinx全文检索功能 | windows下测试

前一阵子尝试使用了一下Sphinx,一个能够被各种语言(PHP/Python/Ruby/etc)方便调用的全文检索系统.网上的资料大多是在linux环境下的安装使用,当然,作为生产环境很有必要部署在*nix环境下,作为学习测试,还是windows环境比较方便些. 本文旨在提供一种便捷的方式让Sphinx在windows下安装配置以支持中文全文检索,配置部分在linux下通用. 一.关于Sphinx Sphinx 是一个在GPLv2 下发布的一个全文检索引擎,商业授权(例如, 嵌入到其他程序中)需

Coreseek/sphinx全文检索的了解

Coreseek/sphinx全文检索的了解 概述: 全文检索是一种将文件中所有文本与检索项匹配的文字资料检索方法,全文检索是将存储于数据库中整本书.整篇文章中的任意内容信息查找出来的检索.它可以根据需要获得全文中有关章.节.段.句.词等信息,也可以进行各种统计和分析. 定义: 全文检索易龙天网的设计师认为可以把它划分为二部分: 全文 全文顾名思义:就是全文检索的对象,它可能是一段话,也可能是一片文章,它可能是一个文件比如:word,txt也能是任意一种扩展名结尾的文件 检索 描文章中的每一个词