PostgreSQL索引篇 | TSearch2 全文搜索

news/2024/7/9 20:00:20 标签: c, 数据库, 索引, postgresql, 全文检索
cle class="baidu_pl">
cle_content" class="article_content clearfix">
content_views" class="markdown_views prism-atom-one-dark"> cap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">

PostgreSQL版本为8.4.1
(本文为《PostgreSQL数据库内核分析》一书的总结笔记࿰c;需要电子版的可私信我)
索引篇:

ch2__8">TSearch2 全文搜索

全文搜索(文本搜索)提供了一种可以检索出满足某个查询条件的自然语言文档的能力࿰c;并且还可以根据文档的相关性对文档进行排序。最常见的搜索是找出所有包含给出的查询词的文档࿰c;并且以它们符合查询的程度排序输出。

文本搜索操作符在数据库里已经存在很多年了。PostgreSQL有<code>~code>、<code>~*code>和<code>LIKEcode>操作符用于文本数据类型࿰c;但是它们缺乏许多现代的信息系统需要的重要功能࿰c;比如:

  • 没有语言支持࿰c;不会对文本进行解析。
  • 不提供检索结果的排序(ranking)࿰c;在找到上千个匹配文档的时候࿰c;就不够高效了。
  • 没有索引支持࿰c;所以会比较慢࿰c;因为它们必须为每个查询处理所有的文档。

从PostgreSQL8.3开始提供了文本搜索模块TSearch(Text Search)࿰c;文本搜索提供了一种可以标识满足某个查询的自然语言文档的能力࿰c;并且还可以根据文档的相关性对文档进行排序。

PostgreSQL核心系统提供的TSearch模块提供了对文档(在PostgreSQL里一个文档通常是一个表中的某个元组的一个文本属性࿰c;或是几个属性的组合)及查询条件进行解析的功能࿰c;但并没有提供对解析后的结果进行进一步处理(创建索引c;以支持快速的查找)的功能。

PostgreSQL在扩展模块contrib里面提供了TSearch2来支持这些功能࿰c;TSearch2实现了对文档创建GIN或者GiST索引的支持。本节将分析PostgreSQL核心系统提供的TSearch模块࿰c;其代码位于src/backend/tsearch目录下。

全文索引的创建

全文索引允许对文档进行预处理并且可以保存为用于快速搜索的索引

预处理包括文本解析语义分析词位存储。完成这三个过程后࿰c;解析后的词语信息就存放在TSVector结构中。

  • 从文本解析到词位存储这一系列过程是由函数to_tsvector_byid完成的࿰c;
    • 该函数首先调用parsetext函数对文本进行解析和语义分析࿰c;
    • 然后再调用make_tsvector将词位信息构建成TSVector结构。

下面将对这三个过程依次进行分析。

文本解析

文本解析通过解析器将文档解析成一个个记号(含位置信息࿰c;类型信息)࿰c;该过程涉及的函数在wparser_def.c文件中。
目前PostgreSQL只提供一种解析器࿰c;但它足够处理大多数纯文本及 HTML文件

PostgreSQL中默认的记号对应表如表4-9所示。

c="https://img-blog.csdnimg.cn/direct/1700ccdf60834a73a2aaede851e55f44.png#pic_center" alt="默认解析器记号对照表" />

语义分析

语义分析是对解析器处理过的token文本序列通过参照词典的审核规范成标准的词(lexeme)信息。

词典用于删除那些不应该在搜索中出现的词(屏蔽词)并规范化一些有多重形式的词࿰c;这样同一个词的不同的衍生结果也可以被搜索到。

成功规范化之后的词被称作词位(lexeme)。除了改进搜索质量࿰c;规范化和删除屏蔽词可以减少文档的尺寸࿰c;从而提高性能。下面对各个词典的使用进行举例介绍:

  • <code>Ispellcode>:拼写词典࿰c;例如“likes”将转换为“like”。
  • <code>Simplecode>:简单词典࿰c;例如“A NAUGHTY DOG”将转换为“naughty dog”。
  • <code>Synonymcode>:同义词典࿰c;例如“man”和“person”是同义词。
  • <code>Thesauruscode>:知识词典࿰c;例如“personal computer”将转换为PC。

完成语义分析后࿰c;即得到一个全部处理后得到的单词信息࿰c;这些单词信息保存在ParsedText结构中。

ParsedText结构保存解析后的文本࿰c;其定义如数据结构4.20所示。

c="https://img-blog.csdnimg.cn/direct/ea3958f6e65842658108dfd6a9c157c0.png#pic_center" alt="ParsedText" />

其中的<code>wordscode>指向一个数组࿰c;其中每一个元素都是ParsedWord类型࿰c;用于保存分析后的一个单词࿰c;其定义如数据结构4.21所示。

c="https://img-blog.csdnimg.cn/direct/50ec7e01b24245ebaef45ee0f996dcda.png#pic_center" alt="ParsedWord" />

数据结构4.21中的<code>poscode>和<code>aposcode>指针是用union结构(允许在相同的内存位置存储不同的数据类型࿰c;但只能存在一个)来保存的。

当完成文本解析后࿰c;可能会遇到相同的词出现了多次的情况࿰c;这时会将相同的词合并在一起:

  • 对于只出现一次的词࿰c;使用<code>poscode>来保存其出现的位置即可;

  • 对于出现多次的词࿰c;则使用<code>aposcode>指针来指向一个动态数组来保存所有出现的位置。apos[0]为该词出现的次数࿰c;数组后面的值即为各次出现的位置。

    由于数组的长度是不确定的࿰c;所以使用<code>alencode>字段来确定动态申请内存的空间࿰c;alen初始化为2࿰c;每当apos 的长度不够时࿰c;alen即翻倍࿰c;同时申请新内存将apos数组的长度翻倍。

上面介绍了用于存储语义分析后得到的单词信息的数据结构࿰c;而这整个处理流程是由函数parsetext完成的࿰c;其执行如图4-36所示。

函数parsetext

c="https://img-blog.csdnimg.cn/direct/6e78c5bfc0024d9ea78508e24005f97d.png#pic_center" alt="语义分析流程" />

<code class="prism language-c">class="token comment">/*
 * Parse string and lexize words.
 *
 * prs will be filled in.
 */
class="token keyword">void
class="token function">parsetextclass="token punctuation">(Oid cfgIdclass="token punctuation">, ParsedText class="token operator">*prsclass="token punctuation">, class="token keyword">char class="token operator">*bufclass="token punctuation">, class="token keyword">int buflenclass="token punctuation">)
class="token punctuation">{
	class="token keyword">int			typeclass="token punctuation">,
				lenlemmclass="token punctuation">;
	class="token keyword">char	   class="token operator">*lemm class="token operator">= class="token constant">NULLclass="token punctuation">;
	LexizeData	ldataclass="token punctuation">;
	TSLexeme   class="token operator">*normsclass="token punctuation">;
	TSConfigCacheEntry class="token operator">*cfgclass="token punctuation">;
	TSParserCacheEntry class="token operator">*prsobjclass="token punctuation">;
	class="token keyword">void	   class="token operator">*prsdataclass="token punctuation">;

	cfg class="token operator">= class="token function">lookup_ts_config_cacheclass="token punctuation">(cfgIdclass="token punctuation">)class="token punctuation">;
	prsobj class="token operator">= class="token function">lookup_ts_parser_cacheclass="token punctuation">(cfgclass="token operator">->prsIdclass="token punctuation">)class="token punctuation">;

	prsdata class="token operator">= class="token punctuation">(class="token keyword">void class="token operator">*class="token punctuation">) class="token function">DatumGetPointerclass="token punctuation">(class="token function">FunctionCall2class="token punctuation">(class="token operator">&prsobjclass="token operator">->prsstartclass="token punctuation">,
													 class="token function">PointerGetDatumclass="token punctuation">(bufclass="token punctuation">)class="token punctuation">,
													 class="token function">Int32GetDatumclass="token punctuation">(buflenclass="token punctuation">)class="token punctuation">)class="token punctuation">)class="token punctuation">;

	class="token function">LexizeInitclass="token punctuation">(class="token operator">&ldataclass="token punctuation">, cfgclass="token punctuation">)class="token punctuation">;

	class="token keyword">do
	class="token punctuation">{
		type class="token operator">= class="token function">DatumGetInt32class="token punctuation">(class="token function">FunctionCall3class="token punctuation">(class="token operator">&class="token punctuation">(prsobjclass="token operator">->prstokenclass="token punctuation">)class="token punctuation">,
										   class="token function">PointerGetDatumclass="token punctuation">(prsdataclass="token punctuation">)class="token punctuation">,
										   class="token function">PointerGetDatumclass="token punctuation">(class="token operator">&lemmclass="token punctuation">)class="token punctuation">,
										   class="token function">PointerGetDatumclass="token punctuation">(class="token operator">&lenlemmclass="token punctuation">)class="token punctuation">)class="token punctuation">)class="token punctuation">;
		class="token comment">// 类型合法&&单词长度过长
		class="token keyword">if class="token punctuation">(type class="token operator">> class="token number">0 class="token operator">&& lenlemm class="token operator">>= MAXSTRLENclass="token punctuation">)
		class="token punctuation">{
class="token macro property">class="token directive-hash">#class="token directive keyword">ifdef class="token expression">IGNORE_LONGLEXEME
			class="token function">ereportclass="token punctuation">(NOTICEclass="token punctuation">,
					class="token punctuation">(class="token function">errcodeclass="token punctuation">(ERRCODE_PROGRAM_LIMIT_EXCEEDEDclass="token punctuation">)class="token punctuation">,
					 class="token function">errmsgclass="token punctuation">(class="token string">"word is too long to be indexed"class="token punctuation">)class="token punctuation">,
					 class="token function">errdetailclass="token punctuation">(class="token string">"Words longer than %d characters are ignored."class="token punctuation">,
							   MAXSTRLENclass="token punctuation">)class="token punctuation">)class="token punctuation">)class="token punctuation">;
			class="token keyword">continueclass="token punctuation">;
class="token macro property">class="token directive-hash">#class="token directive keyword">else
			class="token function">ereportclass="token punctuation">(ERRORclass="token punctuation">,
					class="token punctuation">(class="token function">errcodeclass="token punctuation">(ERRCODE_PROGRAM_LIMIT_EXCEEDEDclass="token punctuation">)class="token punctuation">,
					 class="token function">errmsgclass="token punctuation">(class="token string">"word is too long to be indexed"class="token punctuation">)class="token punctuation">,
					 class="token function">errdetailclass="token punctuation">(class="token string">"Words longer than %d characters are ignored."class="token punctuation">,
							   MAXSTRLENclass="token punctuation">)class="token punctuation">)class="token punctuation">)class="token punctuation">;
class="token macro property">class="token directive-hash">#class="token directive keyword">endif
		class="token punctuation">}

		class="token function">LexizeAddLemmclass="token punctuation">(class="token operator">&ldataclass="token punctuation">, typeclass="token punctuation">, lemmclass="token punctuation">, lenlemmclass="token punctuation">)class="token punctuation">;
				class="token comment">// 对token单词调用字典进行处理(语义分析)
		class="token keyword">while class="token punctuation">(class="token punctuation">(norms class="token operator">= class="token function">LexizeExecclass="token punctuation">(class="token operator">&ldataclass="token punctuation">, class="token constant">NULLclass="token punctuation">)class="token punctuation">) class="token operator">!= class="token constant">NULLclass="token punctuation">)
		class="token punctuation">{
			TSLexeme   class="token operator">*ptr class="token operator">= normsclass="token punctuation">;class="token comment">// norms就是已规范后的一些词

			prsclass="token operator">->posclass="token operator">++class="token punctuation">;			class="token comment">/* set pos */

			class="token keyword">while class="token punctuation">(ptrclass="token operator">->lexemeclass="token punctuation">)class="token comment">// 若解析后的单词不为空࿰c;则将词信息存放到Parsed Text结构中
			class="token punctuation">{
				class="token keyword">if class="token punctuation">(prsclass="token operator">->curwords class="token operator">== prsclass="token operator">->lenwordsclass="token punctuation">)
				class="token punctuation">{
					prsclass="token operator">->lenwords class="token operator">*= class="token number">2class="token punctuation">;class="token comment">// words内存空间翻倍
					prsclass="token operator">->words class="token operator">= class="token punctuation">(ParsedWord class="token operator">*class="token punctuation">) class="token function">repallocclass="token punctuation">(class="token punctuation">(class="token keyword">void class="token operator">*class="token punctuation">) prsclass="token operator">->wordsclass="token punctuation">, prsclass="token operator">->lenwords class="token operator">* class="token keyword">sizeofclass="token punctuation">(ParsedWordclass="token punctuation">)class="token punctuation">)class="token punctuation">;
				class="token punctuation">}

				class="token keyword">if class="token punctuation">(ptrclass="token operator">->flags class="token operator">& TSL_ADDPOSclass="token punctuation">)
					prsclass="token operator">->posclass="token operator">++class="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.len class="token operator">= class="token function">strlenclass="token punctuation">(ptrclass="token operator">->lexemeclass="token punctuation">)class="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.word class="token operator">= ptrclass="token operator">->lexemeclass="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.nvariant class="token operator">= ptrclass="token operator">->nvariantclass="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.flags class="token operator">= ptrclass="token operator">->flags class="token operator">& TSL_PREFIXclass="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.alen class="token operator">= class="token number">0class="token punctuation">;
				prsclass="token operator">->wordsclass="token punctuation">[prsclass="token operator">->curwordsclass="token punctuation">]class="token punctuation">.posclass="token punctuation">.pos class="token operator">= class="token function">LIMITPOSclass="token punctuation">(prsclass="token operator">->posclass="token punctuation">)class="token punctuation">;
				ptrclass="token operator">++class="token punctuation">;
				prsclass="token operator">->curwordsclass="token operator">++class="token punctuation">;
			class="token punctuation">}
			class="token function">pfreeclass="token punctuation">(normsclass="token punctuation">)class="token punctuation">;
		class="token punctuation">}
	class="token punctuation">} class="token keyword">while class="token punctuation">(type class="token operator">> class="token number">0class="token punctuation">)class="token punctuation">;class="token comment">// 判断类型是否合法࿰c;不合法的话说明已经分析完所有词了

	class="token function">FunctionCall1class="token punctuation">(class="token operator">&class="token punctuation">(prsobjclass="token operator">->prsendclass="token punctuation">)class="token punctuation">, class="token function">PointerGetDatumclass="token punctuation">(prsdataclass="token punctuation">)class="token punctuation">)class="token punctuation">;
class="token punctuation">}
code>

词位存储

词位存储即为归总语义分析后的单词在文档中的位置࿰c;并将其出现的次数及每个位置存储下来。

TSVector是一种可搜索的数据类型࿰c;它是文档内容的一种表现形式࿰c;是出现在文档中的每个重要单词及其所有位置信息的集合。它通过一种特殊的优化结构进行组织࿰c;从而可方便快速地存取及查找。其定义如数据结构4.22所示。

c="https://img-blog.csdnimg.cn/direct/141a6803b4874fada3b7dc32b6bc0bfb.png#pic_center" alt="TSVector" />

TSVectorData结构中的<code>WordEntrycode>数组用于保存所有的关键字(单词)信息࿰c;由于关键字的数目一开始并不确定࿰c;所以使用一个数组指针࿰c;该数组的实际长度根据关键字的个数在使用时进行分配。

<code>WordEntrycode>的定义见数据结构4.23。(结构体成员后面的数字用来限定成员变量占用的位数)

c="https://img-blog.csdnimg.cn/direct/30279012ff04459db1e375220fb23920.png#pic_center" alt="WordEntry" />

上面分析了TSVector 的数据结构࿰c;词位存储即使用语义分析得到的ParsedText构建TSVector࿰c;该过程由函数make_tsvector完成。其执行流程如图4-37所示。

c="https://img-blog.csdnimg.cn/direct/6ac7996b882e4a4b94d3ae2b8e7df705.png#pic_center" alt="构建 TSVector 流程图" />

<code class="prism language-c">class="token comment">/*
 * make value of tsvector, given parsed text
 */
TSVector
class="token function">make_tsvectorclass="token punctuation">(ParsedText class="token operator">*prsclass="token punctuation">)
class="token punctuation">{
	class="token keyword">int			iclass="token punctuation">,
				jclass="token punctuation">,
				lenstr class="token operator">= class="token number">0class="token punctuation">,
				totallenclass="token punctuation">;
	TSVector	inclass="token punctuation">;
	WordEntry  class="token operator">*ptrclass="token punctuation">;
	class="token keyword">char	   class="token operator">*strclass="token punctuation">;
	class="token keyword">int			stroffclass="token punctuation">;

	prsclass="token operator">->curwords class="token operator">= class="token function">uniqueWORDclass="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">, prsclass="token operator">->curwordsclass="token punctuation">)class="token punctuation">;class="token comment">// 合并相同单词的位置到apos中
	class="token keyword">for class="token punctuation">(i class="token operator">= class="token number">0class="token punctuation">; i class="token operator">< prsclass="token operator">->curwordsclass="token punctuation">; iclass="token operator">++class="token punctuation">)
	class="token punctuation">{
		lenstr class="token operator">+= prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.lenclass="token punctuation">;
		class="token keyword">if class="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.alenclass="token punctuation">)
		class="token punctuation">{
			lenstr class="token operator">= class="token function">SHORTALIGNclass="token punctuation">(lenstrclass="token punctuation">)class="token punctuation">;
			lenstr class="token operator">+= class="token keyword">sizeofclass="token punctuation">(uint16class="token punctuation">) class="token operator">+ prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.posclass="token punctuation">.aposclass="token punctuation">[class="token number">0class="token punctuation">] class="token operator">* class="token keyword">sizeofclass="token punctuation">(WordEntryPosclass="token punctuation">)class="token punctuation">;
		class="token punctuation">}
	class="token punctuation">}

	class="token keyword">if class="token punctuation">(lenstr class="token operator">> MAXSTRPOSclass="token punctuation">)
		class="token function">ereportclass="token punctuation">(ERRORclass="token punctuation">,
				class="token punctuation">(class="token function">errcodeclass="token punctuation">(ERRCODE_PROGRAM_LIMIT_EXCEEDEDclass="token punctuation">)class="token punctuation">,
				 class="token function">errmsgclass="token punctuation">(class="token string">"string is too long for tsvector (%d bytes, max %d bytes)"class="token punctuation">, lenstrclass="token punctuation">, MAXSTRPOSclass="token punctuation">)class="token punctuation">)class="token punctuation">)class="token punctuation">;
	class="token comment">// 根据ParsedText中唯一单词的数目(curwords)去计算TSVector所需空间
	totallen class="token operator">= class="token function">CALCDATASIZEclass="token punctuation">(prsclass="token operator">->curwordsclass="token punctuation">, lenstrclass="token punctuation">)class="token punctuation">;
	in class="token operator">= class="token punctuation">(TSVectorclass="token punctuation">) class="token function">palloc0class="token punctuation">(totallenclass="token punctuation">)class="token punctuation">;class="token comment">// 分配空间
	class="token function">SET_VARSIZEclass="token punctuation">(inclass="token punctuation">, totallenclass="token punctuation">)class="token punctuation">;
	inclass="token operator">->size class="token operator">= prsclass="token operator">->curwordsclass="token punctuation">;class="token comment">// 设置size

	ptr class="token operator">= class="token function">ARRPTRclass="token punctuation">(inclass="token punctuation">)class="token punctuation">;class="token comment">// ARRPTR(x)	( (x)->entries )
	str class="token operator">= class="token function">STRPTRclass="token punctuation">(inclass="token punctuation">)class="token punctuation">;
	stroff class="token operator">= class="token number">0class="token punctuation">;
	class="token keyword">for class="token punctuation">(i class="token operator">= class="token number">0class="token punctuation">; i class="token operator">< prsclass="token operator">->curwordsclass="token punctuation">; iclass="token operator">++class="token punctuation">)class="token comment">// 取ParsedText中下一个(第一个)单词word
	class="token punctuation">{	class="token comment">// 将该单词的信息拷贝到TSVectorData.entries中
		ptrclass="token operator">->len class="token operator">= prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.lenclass="token punctuation">;
		ptrclass="token operator">->pos class="token operator">= stroffclass="token punctuation">;
		class="token function">memcpyclass="token punctuation">(str class="token operator">+ stroffclass="token punctuation">, prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.wordclass="token punctuation">, prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.lenclass="token punctuation">)class="token punctuation">;
		stroff class="token operator">+= prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.lenclass="token punctuation">;
		class="token function">pfreeclass="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.wordclass="token punctuation">)class="token punctuation">;
		class="token keyword">if class="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.alenclass="token punctuation">)class="token comment">// 获取word的alen࿰c;等于0说明没有位置信息
		class="token punctuation">{
			class="token keyword">int			k class="token operator">= prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.posclass="token punctuation">.aposclass="token punctuation">[class="token number">0class="token punctuation">]class="token punctuation">;class="token comment">// 出现次数
			WordEntryPos class="token operator">*wptrclass="token punctuation">;

			class="token keyword">if class="token punctuation">(k class="token operator">> class="token number">0xFFFFclass="token punctuation">)
				class="token function">elogclass="token punctuation">(ERRORclass="token punctuation">, class="token string">"positions array too long"class="token punctuation">)class="token punctuation">;

			ptrclass="token operator">->haspos class="token operator">= class="token number">1class="token punctuation">;
			stroff class="token operator">= class="token function">SHORTALIGNclass="token punctuation">(stroffclass="token punctuation">)class="token punctuation">;
			class="token operator">*class="token punctuation">(uint16 class="token operator">*class="token punctuation">) class="token punctuation">(str class="token operator">+ stroffclass="token punctuation">) class="token operator">= class="token punctuation">(uint16class="token punctuation">) kclass="token punctuation">;
			wptr class="token operator">= class="token function">POSDATAPTRclass="token punctuation">(inclass="token punctuation">, ptrclass="token punctuation">)class="token punctuation">;
			class="token keyword">for class="token punctuation">(j class="token operator">= class="token number">0class="token punctuation">; j class="token operator">< kclass="token punctuation">; jclass="token operator">++class="token punctuation">)
			class="token punctuation">{
				class="token function">WEP_SETWEIGHTclass="token punctuation">(wptrclass="token punctuation">[jclass="token punctuation">]class="token punctuation">, class="token number">0class="token punctuation">)class="token punctuation">;class="token comment">// 设置权重
				class="token function">WEP_SETPOSclass="token punctuation">(wptrclass="token punctuation">[jclass="token punctuation">]class="token punctuation">, prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.posclass="token punctuation">.aposclass="token punctuation">[j class="token operator">+ class="token number">1class="token punctuation">]class="token punctuation">)class="token punctuation">;class="token comment">// 设置位置
			class="token punctuation">}
			stroff class="token operator">+= class="token keyword">sizeofclass="token punctuation">(uint16class="token punctuation">) class="token operator">+ k class="token operator">* class="token keyword">sizeofclass="token punctuation">(WordEntryPosclass="token punctuation">)class="token punctuation">;
			class="token function">pfreeclass="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">[iclass="token punctuation">]class="token punctuation">.posclass="token punctuation">.aposclass="token punctuation">)class="token punctuation">;
		class="token punctuation">}
		class="token keyword">else
			ptrclass="token operator">->haspos class="token operator">= class="token number">0class="token punctuation">;
		ptrclass="token operator">++class="token punctuation">;
	class="token punctuation">}
	class="token function">pfreeclass="token punctuation">(prsclass="token operator">->wordsclass="token punctuation">)class="token punctuation">;
	class="token keyword">return inclass="token punctuation">;
class="token punctuation">}
code>

至此已经介绍了PostgreSQL内核中 TSearch模块提供的全部功能。之前讲过࿰c;PostgreSQL把TSearch2作为一个扩展模块࿰c;提供了对Entry创建GIN或者GiST索引的支持。通过对词位列创建GIN或GiST索引即可实现对文档的全文索引

综上所述࿰c;全文索引的创建流程图可归纳如图4-38所示࿰c;其中创建GIN ( GiST)索引部分用虚线框,表示该步骤是需要用户额外编译并安装TSearch2模块才具有的功能。
由于GIN或者GiST索引结构的不同࿰c;其创建、查询及更新的效率也有不同࿰c;PostgreSQL 8.4.1官方手册上对这两种索引结构的优劣进行了比较:

  • GIN 索引查询速度是GiST的3倍。
  • GiST创建索引的速度是GIN的3倍。
  • GiST索引的更新速度较GIN稍快。
  • GIN索引的空间比GiST索引大2至3倍。

前面介绍了从文本解析一直到创建全文索引的全部过程。索引创建完成后࿰c;即可利用全文索引进行查询。由于查询条件可能是自然语句࿰c;也需要对查询进行一定的处理以获取准确的查询结果。

接下来将介绍如何利用这里建立的全文索引进行查询。

全文索引的查询

全文索引查询之前需要对检索的语句进行处理࿰c;处理过程跟全文索引的创建过程类似࿰c;要对查询语句进行文本解析及语义分析࿰c;将分析后的结果封装成TSquery格式࿰c;然后就可以通过对创建在TSvector上的索引进行匹配查询了。但与创建过程不同的是:查询处理时࿰c;各个处理后的单词需要使用布尔操作符&(与)、│(或)和!(非)进行连接。

TSquery的格式如数据结构4.24所示。

c="https://img-blog.csdnimg.cn/direct/44e25aa846384a1580f838df03d3efa0.png#pic_center" alt="TSQueryData" />

PostgreSQL提供了to_tsqueryplainto_tsquery两个函数用于把查询转换成TSQuery数据类型。

  • to_tsquery的功能是将查询中的关键词在语义分析后转化成可与TSVector进行匹配的数据类型。该函数的参数要求很严格࿰c;它们必须使用布尔操作符“&”(与)、“|”(或)和“!”(非)分隔各个单词。

  • plainto_tsquery对查询的格式要求没有to_tsquery那么严格࿰c;它把未格式化的文本查询转换成TSQuery。文本首先会像在to_tsvector里那样分析和规范化࿰c;然后用布尔操作符“&”(与)将解析后得到的单词进行连接࿰c;最后得到TSQuery。

to_tsqueryplainto_tsquery函数的处理都是通过调用parse_tsquery函数来实现的。

parse_tsquery函数实现了对用户输入的查询条件的解析及语义分析过程࿰c;使用布尔操作符将得到的关键字连接起来。通过调用makepol函数࿰c;将查询语句转换成“波兰表示法”(“Polish notation”࿰c;或称为波兰记法)。

对查询条件的解析过程与4.6.1节中的过程基本一样࿰c;通过调用parsetext函数完成。当读取到查询条件中的操作符时࿰c;只能是上面讲到的三种操作符࿰c;对于其他操作符则报错(plainto_tsquery函数则不会处理用户输入条件中的操作符࿰c;它对查询条件进行解析后࿰c;全部使用“&”(与)操作符连接得到的关键词)。

当得到TSquery后࿰c;即可调用之前创建的全文索引进行查询࿰c;全文索引查询的总体流程如图4-39所示。

c="https://img-blog.csdnimg.cn/direct/0ebcf14c31a649ecb678a50de09c8ab8.png#pic_center" alt="全文索引查询流程" />

查询结果处理

上面分析了全文索引的创建和查询处理过程。数据库系统提供了@@操作符࿰c;可以对TSVector(或处理后的TSQuery)结构中的entry进行比较。下面给出一个使用@@操作符进行全文搜索的例子。

假设现在在数据库中有一个messages表࿰c;其字段内容如表4-10所示。

c="https://img-blog.csdnimg.cn/direct/a035f22283fd4079aad3eac5fc131e27.png#pic_center" alt="messages表" />

如果要从上表中查出属性strMessage中包含“test”或者“king”的文档信息࿰c;其tsearch语句如下:

<code class="prism language-sql">class="token keyword">SELECT idclass="token punctuation">, strtopic class="token keyword">FROM Messages
class="token keyword">WHERE to_tsvectorclass="token punctuation">(strMessageclass="token punctuation">) @@ to_tsqueryclass="token punctuation">( class="token string">'test | king ' class="token punctuation">)class="token punctuation">;
code>

PostgreSQL会对messages表中的strMessage字段按4.6.1节中的介绍进行处理࿰c;然后对“test | king"采用全文索引的查询中的to_tsquery函数进行处理࿰c;再调用@@操作符进行匹配。最后࿰c;返回匹配成功的结果࿰c;如表4-11所示。

c="https://img-blog.csdnimg.cn/direct/f1ada7304ccd4449afaeda9d21314d4e.png#pic_center" alt="messages表" />

对全文搜索的结果࿰c;TSearch还提供了一些功能对结果进行处理࿰c;包括权重设置、结果排序以及结果高亮显示࿰c;下面将对这3种功能进行介绍。

  • 设置文档部分的权重

    权重用于标记单词来自于文档的哪个区域࿰c;比如标题和开头的摘要࿰c;这样就可以以不同的方式对待不同权重的单词。可以通过调用函数tsvector_setweight将一个TSVector中所有的关键字都设置为指定的权重。

  • 对查询结果排序

    排序主要是将匹配成功的搜索结果与查询表达式进行相关性比较࿰c;将最相关的结果排在前面。相关性是评判文档与特定的查询之间相关程度的一个衡量标准。如果有很多匹配的项࿰c;那么相关性高的项应该排在前面。

    ts_rankts_rank_ed函数都可以用来对查询结果进行排序。

  • 高亮显示

    呈现搜索结果时࿰c;最好是显示每个文档的一部分以及它和查询之间是如何关联的。通常࿰c;搜索引擎会显示查询关键词所在的文档片段࿰c;并且在其中将查询关键词高亮显示。PostgreSQL也提供一个函数ts_headline实现这个功能。

    ts_headline接受一个文档以及对应的查询࿰c;然后返回一个文档的摘要。在摘要里面࿰c;查询是高亮显示的。ts_headline使用原始的文档࿰c;而不是TSVector摘要࿰c;所以它可能比较慢࿰c;因此要小心使用。

索引篇小结

索引是提高数据库性能的常用方法࿰c;它可以令数据库服务器以更快的速度查找和检索特定的行。不过索引也增加了数据库系统的负荷、成本࿰c;因此应该恰当地使用它们。索引的优劣不仅仅与索引的查询效率有关࿰c;同时与索引创建速度࿰c;更新速度࿰c;索引大小等因素有关。以上分析的几种索引使用环境不同࿰c;相同环境下使用的优劣也各不相同。于是࿰c;为了方便用户对特殊数据类型数据的查询࿰c;PostgreSQL中提供了索引模板可方便用户对索引的扩展以支持自定义数据类型上的索引创建与查询。

通过使用索引c;能够快速查找数据库中的数据。但在添加索引的同时࿰c;会增加数据库系统的负荷;在增删修改数据时࿰c;维护索引的一致性也需要一定的时间和空间。由于索引给查找数据带来的巨大的性能提升࿰c;因此也得到了广泛的应用。在实际的使用中࿰c;应该合理权衡使用索引带来的利弊࿰c;恰当地使用索引


http://www.niftyadmin.cn/n/5420923.html

相关文章

C语言基础练习——Day04

目录 选择题 编程题 错误的集合 密码检查 选择题 1、设变量已正确定义&#xff0c;以下不能统计出一行中输入字符个数&#xff08;不包含回车符&#xff09;的程序段是 A n0;while(chgetchar()!\n)n; B n0;while(getchar()!\n)n;C for(n0;getchar()!\n;n); D n0;for(chgetchar(…

做了近1年半的软件测试点点点,觉得我在浪费时间……

我工作八年整&#xff0c;之前做过开发&#xff0c;现在正在做测试&#xff0c;发现人们对测试非常轻视&#xff0c;究其原因就在于测试入门的门槛太低了&#xff0c;导致人们认为测试仅仅会点页面。关于测试我想说一下我的看法。 01、页面功能测试机能 1、按照产品给的需求文档…

wpf prism DryIoc批量注册服务

1.首先引入Scrutor包 2.在App.xaml.cs中注册 protected override IContainerExtension CreateContainerExtension(){var serviceCollection new ServiceCollection();serviceCollection.AddMemoryCache();serviceCollection.AddAutoMapper();serviceCollection.RegisteServi…

python开发100问?

Python是什么&#xff1f;它有什么特点&#xff1f;Python的历史是什么样的&#xff1f;Python有哪些应用领域&#xff1f;Python的基本数据类型有哪些&#xff1f;Python中的变量命名规则是什么&#xff1f;如何在Python中进行注释&#xff1f;Python中的缩进是什么意思&#…

安卓studio安装

安卓studio安装 2024.3.11官网的版本&#xff08;有些翻墙步骤下载东西也解决了&#xff09; 这次写的略有草率&#xff0c;后面会更新布局的&#xff0c;因为截图量太大了&#xff0c;有需要的小伙伴可以试着接受一下哈哈哈哈 !(https://gitee.com/jiuzheyangbawjf/img/raw/ma…

数据容器化,显著增强数据安全性

数据容器化涉及同一端点设备中的应用程序&#xff0c;这些应用程序充当唯一且隔离的数据“容器”。在数据丢失防护方面&#xff0c;可以通过将组织内经常用于处理关键数据的受信任应用程序标记为企业友好&#xff0c;将其委派为敏感信息的数据容器。然后&#xff0c;与其保护整…

ms office学习记录13:Excel学习记录㈦对应配套作业(三)

⒈表格图表&#xff08;与word中的考法基本一致&#xff09; 按ctrl键不放可同时选择多个不连续单元格&#xff0c;连续选择一列后按住ctrl键不放再操作可同时连续选择多列 注意看清是创建新图表工作表还是单纯插入图表&#xff0c;如要创建新图表工作表则在设计好后选中整个…

CVE-2021-31440:eBPF verifier __reg_combine_64_into_32 边界更新错误

文章目录 前言漏洞分析构造 vuln reg 漏洞利用漏洞修复参考 前言 影响版本&#xff1a;Linux 5.7 ~ 5.11.20 8.8 编译选项&#xff1a;CONFIG_BPF_SYSCALL&#xff0c;config 所有带 BPF 字样的编译选项。General setup —> Choose SLAB allocator (SLUB (Unqueued Allocat…