PostgreSQL索引篇 | GIN索引 (倒排索引)

GIN索引 倒排索引

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

  • PostgreSQL索引篇 | BTree
  • PostgreSQL索引篇 | GiST索引
  • PostgreSQL索引篇 | Hash索引
  • PostgreSQL索引篇 | TSearch2 全文搜索

概述

GIN(Generalized Inverted Index,通用倒排索引)是一个存储对(key,posting list)集合的索引结构

其中“key”是一个键值,而“posting list”是一组出现过“key”的位置。

如 (“hello”,“14:17,23:1,……”)中,“hello”表示一个键值,而“14:17,23:1,……”则表示“hello”这个键值出现的位置。

每一个位置又由“元组ID:位置”来表示,位置“14:17”说明“hello”在第14号元组的被索引属性中第17个位置出现。每一个被索引的属性值都可能包含多个键值,因此同一个元组的ID可能会出现在多个存储对的“posting list”中。通过这种索引结构可以快速地查找到包含指定关键字的元组,因此GIN索引特别适合于支持全文搜索。

而开发GIN索引的主要目的就是为了让PostgreSQL能支持功能高度可扩展的全文搜索。

GIN索引具有很好的可扩展性,允许在开发自定义数据类型时由该数据类型的领域专家(而不是数据库专家)设计适当的访问方法,这些访问方法只需要考虑对于数据类型本身语义的处理,而GIN索引自身可以处理并发操作、记录日志、搜索树结构等操作。

定义一个GIN访问方法所要做的就是实现五个用户定义的方法,这些方法定义了键值、键值与键值之间的关系、被索引值、能够使用索引的查询及部分匹配。简而言之,GIN结合了扩展性、普遍性、代码重用、清晰的接口等特点。

一个GIN索引需要实现的五个方法如下:

  • compare方法:比较两个键值a和b,然后返回一个整数值,返回负值表示a小于b,返回0表示a等于b,返回正值表示a大于b。

    其原型如下:

    int compare (Datum a,Datum b)

  • extractValue方法:根据参数inputValue生成一个键值数组,并返回其指针,键值数组中元素的个数存放在另一个参数nkeys 中。

    其原型如下:

    Datum* extractValue (Datum inputvalue,uint32 *nkeys)

  • extractQuery方法:根据一个查询(由参数query 给定)生成一个用于查询的键值数组,并返回其指针。

    • extractQuery通过参数n指定的操作符策略号来决定query的数据类型以及需要提取的键值,返回键值数组的长度存放在nkeys参数中。如果query中不包含键值,则extractQuery将根据操作符的语义在nkeys中存储0或-1。
    • nkeys值为0表示索引中所有值都能满足query,因此将执行完全索引扫描,例如当query为空字符串时就会发生此种情况。当nkeys值为-1时表明索引中没有键值能匹配查询,因此可以跳过索引扫描。
    • 当索引支持部分匹配时,输出参数pmatch用于记录返回的键值数组中每一个键值是否要求部分匹配。extractQuery将分配一个长度为nkeys 的布尔型数组,将这个数组的指针通过pmatch输出,数组中的每一个元素都记录了键值数组中对应位置键值的部分匹配情况,如果为真就表示该键值要求部分匹配。
    • 输出参数extra_data用来向consistent和comparePartial方法传递用户定义需要的数据,extra_data指向一个长度为nkeys 的指针数组,extractQuery可以在每一个数组元组所指向的内存空间存放任何数据。如果extra_data被设为非空,则extra_data指向的整个数组都将被传递给consistent方法,而其中适当的元素将被传递给comparePartial方法。

    其原型如下:

    Datum *extractQuery (Datum query, int32 * nkeys,strategyNumber n, bool * *pmatch,Pointer * *extra_data)
    
  • consistent方法:该方法用于检查索引值是否满足查询。

    • 如果查询与索引满足策略号为n的操作符则返回真,如果返回真且输出参数recheck也设置为真则说明查询和索引可能满足,还需要进一步检查。check 数组的长度必须与先前由extractQuery为该查询返回的键值数量相同。

    • 如果索引值包含相应的查询,那么check 数组中的每一个元素都是真。也就是说,如果check[i]为真,那么extractQuery返回的键值数组的第i个键值存在于索引值当中。

    • 如果GIN索引的比较是精确的,recheck将被设置为假,否则recheck将被设置为真,通过索引找到的基表元组还需要进行是否满足操作符的检查。

    图4-33为一个consistent方法实现的实例,图中查询条件中的“&”符号表示“与”,而“|”符号则表示“或”。

    其原型如下:

    bool consistent (bool check[],strategyNumber n,Datum query, int32 nkeys,Pointer extra_data[], bool * recheck)
    

    consistent方法实现的实例

  • comparePartial方法:将部分匹配的查询与索引值进行比较,返回值为负值表示两者不匹配,但继续扫描索引;

    返回值为0表示两者匹配;返回值为正值表示停止扫描。

    n是操作符的策略号;

    extra_data是由函数extractQuery传递过来的数组。

    这个函数是在PostgreSQL 8.4.1中新增的函数。该方法的原型如下:

    int conparePartial (Datum partial_key,Datum key,strategyNumber n, Pointer extra_data)
    

添加一种新的数据类型

如果要在 PostgreSQL中添加一种新的数据类型并且让GIN索引能够支持该数据类型,则需要完成以下步骤:

  • 添加数据类型:

    • ①实现新数据类型的输入输出函数,并通过CREATE OR REPLACE FUNCTION语句将其注册到数据库内部,这一步操作将会在pg.proc系统表中为每一个函数增加一个元组。
    • ②利用上一步注册的输入输出函数,通过“CREATE TYPE”语句创建数据类型,这一步会在pg_type系统表中为新数据类型增加一个元组,并通过元组中的属性值与上一步创建的输入输出函数关联起来。
    • ③为新数据类型实现并注册各种操作符所需的函数,然后通过“CREATE OPERATOR”语句为新数据类型创建操作符。
  • 为新数据类型实现GIN索引所需要的5种支持函数(compare、extractValue等),

    并通过CREATE OR REPLACE FUNCTION语句将这些函数注册到数据库内部。

  • 用“CREATE OPERATOR CLASS”语句为新的数据类型创建一个操作符类,该SQL语句需指定GIN索引所需要的5个支持函数。

完成上述步骤之后,就可以用以下SQL语句在新数据类型上创建GIN索引了:
CREATE INDEX index_name ON table_name (column_name) USING GIN;

GIN索引的组织结构

GIN索引包括以下四个部分:

  • Entry:是GIN索引中的一个元素(可以认为是一个词位),Entry也可以理解为GIN索引中的一个键值。
  • Entry Tree:在一些Entry上构建的B-Tree。
  • Posting Tree:在一个Entry出现的物理位置上构建的 B-Tree。
  • Posting List:一个Entry出现的物理位置的列表。

GIN索引包含一个构建在Entry(键值)上的B-Tree索引 (Entry Tree),“Entry Tree”的内部节点与普通B–Tree索引的内部节点一样,不同的是叶子节点中索引项的指针指向不同,普通B-Tree的叶子节点中索引项指针指向基表元组的物理存储位置,而GIN索引的叶子节点中索引项的指针指向位置分两种情况:

  • 如果该叶子节点中索引项内包含的键值(Entry)所出现的物理位置大于宏TOAST_INDEX_TARCET所指定的值,则在这些物理位置上构建“Posting Tree”,然后将索引项指针指向“Posting Tree”的根节点。
  • 否则,叶子节点中索引项指针直接指向“Posting List”。

也就是说,如果某个Entry出现的位置较多(超过了TOAST_INDEX_TARCET的值),则在其出现的位置(也就是“Posting List”)上再创建一个B-Tree结构,以加快查找的速度。

一个GIN索引的组织结构示例如图4-34所示:

GIN索引的组织结构

在实现上,“Entry Tree”的非叶子节点部分和B–Tree索引类似,都是以一个页面来组织索引元组。

但“Entry Tree”叶子节点和普通的B-Tree索引页面结构有些区别:

  • 若叶子节点中索引元组(IndexTuple,见数据结构4.3)的位置信息采用Posting List形式存储,则在该索引元组键值之后的空间连续存放“Posting List”,这样可快速获取到该键值所有的位置信息。由于是连续存储(不需要访问其他节点),因此索引元组的t_tid字段的ip_blkid部分用于存储“Posting List”的长度(即该键值出现了多少次),而ip_posid部分用于记录该索引元组的大小(不包括“Posting List”的大小)。
  • 若叶子节点中索引元组的位置信息采用Posting Tree形式存储(空间换时间),则该索引元组的t_tid字段的ip_blkid部分记录了“Posting Tree”根节点所在的磁盘块号,而t_tid字段的ip_posid部分则设置为GIN_TREE_POSTING,用于与“Posting List”区分开来。

通过这种方式,巧妙地使用索引元组中的t_tid字段,使得对“Posting List”和“Posting Tree”的操作得到统一,根据t_tid字段中的ip_posid部分的取值可以区分“Posting List”和“Posting Tree”,然后根据两种不同的类型调用不同的方法来读取位置信息。PostgreSQL提供了一系列的宏定义用于支持这些操作。

GIN索引中共设置了5种页面类型,如表4-8所示。

GIN索引页面类型

元页

其中,GIN索引的元页主要用来管理索引创建过程中待索引的页面信息,其定义如数据结构4.14所示。

GinMetaPageData

Pending List

在创建GIN索引时,首先会将所有所有待索引的Entry及其位置信息统计出来,并将这些信息存储在GIN_LIST页面中,GIN_LIST页面将构成一个双向链表(也称为“Pending List”),在元页中通过head、tail两个字段分别指向这个链表的头部和尾部。

在存储Entry及其位置信息时,先申请一个GIN_LIST页面,将其填满之后链接在“Pending List”尾部,然后再申请下一个GIN_LIST页面进行填充。因此在“Pending List”中,只有尾部的页面可能是不满的,其他页面都是被填充满的,在元页中用tailFreeSize字段记录最尾部页面的剩余空间大小。在创建GIN索引结构时,会依次读取出“Pending List”中页面里的信息,并插入到索引结构中。

索引操作

在PostgreSQL8.4.1中,GIN索引可以支持tsvector和所有内置数据类型的一维数组。

另外,在源代码的contrib目录(含很多扩展模块)下还有btree-gin、hstore、intarray和pg_trgm四个子目录,每一个子目录中的代码都可以用来在数据库系统中增加一种新的数据类型并让GIN索引能支持它,其基本实现过程和上面(GIN添加一种新的数据类型)所介绍的一致。

索引的创建

GIN索引的创建过程就是从基表中依次取出基表元组,然后从基表元组构建出若干个Entry及其“Posting List”,然后将这些Entry插入到GIN索引结构中。

在实现中,GIN索引的创建函数并不是构建出一个Entry就立即将其插入到GIN索引中,而是建立了一个“蓄积池”,构建出的Entry都将先放入“蓄积池”,当“蓄积池”填充到一定程度之后才会将其中缓存的Entry依次插入到GIN索引中去。

GinBuildState类型

GIN索引的创建函数使用了一个GinBuildState类型(数据结构4.15)的变量来对整个创建过程的状态加以控制。

GinBuildState

  • ginstate字段存放当前使用的GIN索引相关的5个支持函数信息,这些信息是与要创建GIN索引的基表属性的数据类型相关的。

  • accum字段则指向索引创建过程中所使用的“蓄积池”,其数据类型为BuildAccumulator

BuildAccumulator类型

BuildAccumulator

BuildAccumulator中的maxdepthallocatedMemory两个字段反映了“蓄积池”中填充Entry的程度,如果maxdepth超过GIN_MAX_TREE_DEPTH(值为100)或者allocatedMemory超过一个阈值(阈值为16384*1024),则创建过程会先将“蓄积池”中的Entry都插入到GIN索引中,然后清空“蓄积池”。

EntryAccumulator类型

EntryAccumulator

对于EntryAccumulator结构中的字段说明如下:

  • attnum字段:由于GIN支持多属性索引,attnum记录了Entry出现在第几个被索引的属性中,该字段从1开始递增。也就是说,如果一个关键词出现在同一个元组的两个不同属性中,则会为该关键词创建两个EntryAccumulator添加到树中。

  • length字段:由于一个Entry出现的次数是无法预知的,所以list字段所指向的数组长度是动态变化的,当number(频率)大于等于length(目前分配给list的长度)时,即将length翻倍,同时list的长度也翻倍

  • shouldSort字段:若该Entry在多个地方出现,依次将读取到的位置信息插入到list字段(Posting List)中,由于后面插入的位置可能会使得list中的位置变成无序的,就需要标记shouldSort字段为真,在插入GIN索引时会根据这个字段的值来判断是否需要进行排序。

    注:在插入出现位置到“Posting List”时并不排序,而是在构建整个GIN索引结构时,根据该字段判断是否需要排序。

需注意:

  • 对于同一个Entry,即使在多个元组的同一个属性中出现(attnum相同),也都是保存在同一个EntryAccumulator中,其出现的不同位置保存在List结构中。

    说明一个EntryAccumulator会有这个Entry在不同元组、同一个属性中出现的位置。

  • 但是同一个Entry如果出现在不同的属性(attnum不同)中,则要用不同的EntryAccumulator来表示。

EntryAccumulator通过其let和right字段指向其左右子节点,EntryAccumulator构成的二叉树是有序的,左子节点的Entry比父节点的Entry“小”,父节点的Entry比右子节点的Entry“小”。这里的大小可通过compareAttEntries函数来进行比较。

PostgreSQL中提供了ginInsertRecordBA函数将来自于同一个元组的同一个属性的多个Entry插入到“蓄积池”。ginInsertRecordBA插入Entry所采用的策略如下:

  1. 将Entry数组进行排序。
  2. 首先将Entry数组中间元素插入到排序二叉树中,然后采用同样的策略递归处理Entry数组的左半部分右半部分

例如,要用ginInsertRecordBA函数插人以下的Entry数组(这里没有列举Entry出现的位置,只给出了关键词):

待插入的Entry数组(已排序):ahstract, hinary, hash, hello, index, tree,world`

实际ginInsertRecordBA执行的过程如下:

  1. 插入数组中间元素hello,左右部分分别作为左右孩子,递归插入左右两个数组:

    ahstract, hinary, hashindex, tree,world

    树中此时只有一个根节点:hello

  2. 递归地对左数组取出中间元素,左右部分分别作为左右孩子,此时二叉树的结构为:

    GIN左数组

  3. 递归处理右数组并插入到树中后,二叉树的结构为:

    GIN树例子

采用这种插入策略的好处是能够尽量保证插入记录后的二叉树趋于平衡,不会使得其高度太大影响查找效率。

我总结下上面提到的EntryEntryAccumulator的关系图:(后者数量多于前者)
注:蓝色部分表示查找条件为:身高=181或身高=170时找到的所有EntryAccumulator。
Entry可能包含了来自不同元组、不同属性的key。

GIN索引元组存储示例

ginbuild函数

创建GIN索引函数是ginbuild函数,其流程如下:

  1. 调用initGinState初始化创建状态变量buildstate(GinBuildState类型)的ginstate字段,该函数会根据当前要创建的索引的信息以及被索引属性的信息从系统表中抽取5个支持函数的信息用于填充ginstate字段的内容。

  2. 初始化GIN索引的元页,并初始化buildstate的其他字段,包括置indtuples为0、创建临时内存上下文和函数调用内存上下文。

  3. 调用ginInitBA初始化“蓄积池”(即buildstate中的accum字段)。

  4. 调用IndexBuildHeapScan对基表扫描,并将ginBuildCallback函数的指针传递给IndexBuildHeapScan。每扫描到一个基表元组,IndexBuildHeapScan都将其被索引属性的值解析出来然后传递给ginBuildCallback处理,在ginBuildCallback中将进行以下处理:

    • ①对于每一个被索引属性的值,调用ginHeapTupleBulkInsert对其进行处理,
      该函数会调用GIN索引的extractValue方法将被索引属性的值解析成若干个Entry(一个属性值中可能包含多个关键词),并将这些获得的Entry插入到“蓄积池”中(ginInsertRecordBA函数),该函数值将返回获得的Entry的个数,这个数量将被累加到buildstate的indtuples字段中。

      for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++)if (!isnull[i])buildstate->indtuples += ginHeapTupleBulkInsert(buildstate,(OffsetNumber) (i + 1), values[i],&htup->t_self);
      
    • ②对“蓄积池”的填充情况进行检查,如果allocatedMemorymaxdepth字段的值超过阈值则循环调用ginGetEntry从“蓄积池”中取出一个Entry,并将其用ginEntryInsert插入到GIN索引中。

    • ③当“蓄积池”中的Entry都已经被插入到GIN索引中后,通过重置buildstate中的临时内存上下文来清空“蓄积池”,然后调用ginInitBA重新初始化“蓄积池”。

      /* If we've maxed out our available memory, dump everything to the index *//* Also dump if the tree seems to be getting too unbalanced */if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L ||buildstate->accum.maxdepth > GIN_MAX_TREE_DEPTH){ItemPointerData *list;Datum		entry;uint32		nlist;OffsetNumber attnum;while ((list = ginGetEntry(&buildstate->accum, &attnum, &entry, &nlist)) != NULL){/* there could be many entries, so be willing to abort here */CHECK_FOR_INTERRUPTS();ginEntryInsert(index, &buildstate->ginstate, attnum, entry, list, nlist, TRUE);}MemoryContextReset(buildstate->tmpCtx);ginInitBA(&buildstate->accum);}
      
  5. 当IndexBuildHeapScan扫描完成后,“蓄积池”中有可能还有一部分Entry没有被插入到GIN索引中( allocatedMemorymaxdepth字段的值都没有超过阈值),同样循环调用ginGetEntry获取Entry再通过ginEntryInsert插入GIN索引中。

  6. 返回统计信息,包括被索引的基表元组以及索引元组的数目。

Datum
ginbuild(PG_FUNCTION_ARGS)
{Relation	heap = (Relation) PG_GETARG_POINTER(0);Relation	index = (Relation) PG_GETARG_POINTER(1);IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);IndexBuildResult *result;double		reltuples;GinBuildState buildstate;Buffer		RootBuffer,MetaBuffer;ItemPointerData *list;Datum		entry;uint32		nlist;MemoryContext oldCtx;OffsetNumber attnum;if (RelationGetNumberOfBlocks(index) != 0)elog(ERROR, "index \"%s\" already contains data",RelationGetRelationName(index));// 初始化创建状态变量buildstate的`ginstate`字段,填充5个函数initGinState(&buildstate.ginstate, index);/* initialize the meta page */MetaBuffer = GinNewBuffer(index);/* initialize the root page */RootBuffer = GinNewBuffer(index);START_CRIT_SECTION();// 初始化GIN索引的元页GinInitMetabuffer(MetaBuffer);MarkBufferDirty(MetaBuffer);GinInitBuffer(RootBuffer, GIN_LEAF);MarkBufferDirty(RootBuffer);if (!index->rd_istemp){XLogRecPtr	recptr;XLogRecData rdata;Page		page;rdata.buffer = InvalidBuffer;rdata.data = (char *) &(index->rd_node);rdata.len = sizeof(RelFileNode);rdata.next = NULL;recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX, &rdata);page = BufferGetPage(RootBuffer);PageSetLSN(page, recptr);PageSetTLI(page, ThisTimeLineID);page = BufferGetPage(MetaBuffer);PageSetLSN(page, recptr);PageSetTLI(page, ThisTimeLineID);}UnlockReleaseBuffer(MetaBuffer);UnlockReleaseBuffer(RootBuffer);END_CRIT_SECTION();/* build the index 初始化buildstate的其他字段,包括置`indtuples`为0*/buildstate.indtuples = 0;/* 创建 临时内存上下文 和 函数调用内存上下文* create a temporary memory context that is reset once for each tuple* inserted into the index*/buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,"Gin build temporary context",ALLOCSET_DEFAULT_MINSIZE,ALLOCSET_DEFAULT_INITSIZE,ALLOCSET_DEFAULT_MAXSIZE);buildstate.funcCtx = AllocSetContextCreate(buildstate.tmpCtx,"Gin build temporary context for user-defined function",ALLOCSET_DEFAULT_MINSIZE,ALLOCSET_DEFAULT_INITSIZE,ALLOCSET_DEFAULT_MAXSIZE);// buildstate.accum.ginstate = &buildstate.ginstate;// 初始化“蓄积池”ginInitBA(&buildstate.accum);/* 扫描基表* 每扫描到一个基表元组,都将其被索引属性的值解析出来然后传递给ginBuildCallback处理* Do the heap scan.  We disallow sync scan here because dataPlaceToPage* prefers to receive tuples in TID order.*/reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,ginBuildCallback, (void *) &buildstate);/* dump remaining entries to the index 当IndexBuildHeapScan扫描完成后,“蓄积池”中有可能还有一部分Entry没有被插入到GIN索引中(`allocatedMemory`和`maxdepth`字段的值都没有超过阈值),同样循环调用ginGetEntry获取Entry再通过**ginEntryInsert**插入GIN索引中。*/oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);while ((list = ginGetEntry(&buildstate.accum, &attnum, &entry, &nlist)) != NULL){/* there could be many entries, so be willing to abort here */CHECK_FOR_INTERRUPTS();ginEntryInsert(index, &buildstate.ginstate, attnum, entry, list, nlist, TRUE);}MemoryContextSwitchTo(oldCtx);MemoryContextDelete(buildstate.tmpCtx);/* 返回统计信息,包括被索引的基表元组以及索引元组的数目* Return statistics*/result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));result->heap_tuples = reltuples;result->index_tuples = buildstate.indtuples;PG_RETURN_POINTER(result);
}
ginEntryInsert函数

将Entry及其位置信息插入到GIN索引结构中,是通过调用ginEntryInsert函数来完成的,该函数的流程如图4-35所示。

ginEntryInsert插入一个Entry到GIN索引中

在ginEntryInsert中,待插入的Entry用一个数据结构GinBtreeData来管理,其定义如下所示:

typedef struct GinBtreeData
{/* search methods 查找schema使用的函数指针*/BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *);bool		(*isMoveRight) (GinBtree, Page);bool		(*findItem) (GinBtree, GinBtreeStack *);/* insert methods 插入schema使用的函数指针*/OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber);BlockNumber (*getLeftMostPage) (GinBtree, Page);bool		(*isEnoughSpace) (GinBtree, Buffer, OffsetNumber);void		(*placeToPage) (GinBtree, Buffer, OffsetNumber, XLogRecData **);Page		(*splitPage) (GinBtree, Buffer, Buffer,OffsetNumber,XLogRecData **);void		(*fillRoot) (GinBtree, Buffer, Buffer, Buffer);bool		searchMode; //是否为查找模式Relation	index; //索引表GinState   *ginstate; //用于记录GIN索引提供的5种扩展方法bool		fullScan; //是否为完全扫描bool		isBuild; //该Entry对应的位置信息上是否创建了B-TreeBlockNumber rightblkno; //该Entry所在页面的下一个(右)页面/* Entry options */OffsetNumber entryAttnum; //Entry在索引元组中对应属性编号Datum		entryValue; //Entry的键值IndexTuple	entry; //Entry对应的索引元组bool		isDelete; //Entry是否被删除/* Data (posting tree) option 以下是与位置信息相关字段*/ItemPointerData *items; //Entry的位置数组 “Posting List”uint32		nitem; //位置数组的长度 根据nitem来判断是否需要构建“Posting Tree”。uint32		curitem; //当前使用的“位置”PostingItem pitem; //保存叶子节点页面的页面号以及一个物理地址
} GinBtreeData;

注意,GinBtreeData数据结构不仅在GIN索引的创建过程中会使用,在使用GIN索引进行查询插入时都会使用到。

其中的items字段在创建索引时用于保存该Entry所有的位置信息,即保存“Posting List”,然后根据位置数组的长度来判断是否需要构建“Posting Tree”。

可以看到,GinBtreeData数据结构中定义了9个函数指针,这些函数指针指向的函数实现了对GIN索引结构的查找、插入、分裂等操作,对于每一个函数指针,既可以定义对Entry节点操作的函数,也可以指向对位置信息进行操作的函数。

每一个函数的功能说明如下:

  1. findChildPage:在GinBtreeStack类型参数的buffer字段对应的非叶子层页面中找到Entry(由GinBtreeData类型参数表示)对应的索引元组(由于是非叶子层页面,索引元组中的指针指向下一层的孩子页面),并返回索引元组指向的孩子页面的块号。
  2. isMoveRight:比较Page类型参数对应的页面中最后一个索引元组与GinBtree对应的Entry的值大小,如果小于则返回真(表示该页面应该右移),否则返回假。
  3. finditem:在GinBtreeStack类型参数的buffer字段对应的叶子层页面中查找Entry (由GinBtreeData类型参数表示)对应的索引元组,若存在,将索引元组在页面中的偏移量赋给GinBtreeStack类型参数的off字段并返回真,否则返回假。
  4. findChildPtr:在Page类型参数对应的页面中查找孩子页面号为BlockNumber的索引元组的页面内偏移量并返回。
  5. getLeftMostPage:返回非叶子层页面中第一个索引元组指向的页面号。
  6. isEnoughSpace:判断Buffer类型参数指向的缓冲区中是否有足够空间存储GinBtree对应的索引元组,如果有足够空间则返回真,否则返回假。
  7. placeToPage:将GinBtree中的索引元组写到缓冲区中,OffsetNumber类型参数为写入缓冲区的起始偏移量,并写日志。
  8. splitPage:将GinBtree中的索引元组插入到第一个参数指定的缓冲区中,并将该缓冲区分裂成两个大小几乎相当的缓冲区,分裂产生的新缓冲区放在第二个参数中,返回第一个缓冲区的页面号,最后写日志。
  9. fillRoot:将第三、四个参数指定的两个缓冲区最右端的索引元组填充到第二个参数指定的缓冲区中,并将被填充索引元组的指针分别指向第三、四个参数对应的页面号。

可以看出,这些函数中很多都使用了GinBtreeStack数据结构,该结构用于记录索引结构在一次查找过程中从根节点到叶子节点的路径,当插入一个Entry引起节点分裂后,需要使用该结构向上回溯调整

GinBtreeStack结构的定义见数据结构4.19。

GinBtreeStack结构

对于GIN索引的插入,若一次插入了很多元组,则会调用ginHeapTupleFastInsert函数进行批量插入,该函数的执行流程与索引创建流程类似,这里就不再赘述。

在大多数情况下,若更新的数据量很大,GIN索引的“Posting List”结构可能较长,向GIN索引插入的速度较慢。因此,对于向表中大量插入的操作,建议先删除GIN索引,在完成插入之后再重建它。

索引查询

GIN索引的查询通过关键词与Entry的匹配操作来查找其所在的元组。在查找的过程中按照B-Tree结构进行搜索,调用consistent方法来判断是否找到。GIN索引没有提供返回单个元组的函数(类似B-Tree索引的btgettuple函数),只提供了位图查询的方式,也就是说,对GIN索引的查询只能得到一个位图,其中包含了符合查询条件的元组的物理位置。

开发GIN索引的目的是让PostgreSQL支持高度可扩展的全文索引。我们常常会遇到全文索引返回海量结果的情形,在查询高频词时得到的很大的结果集其实并没有什么用,因为从磁盘读取大量记录并对其进行排序会消耗大量资源。

为了易于控制这种情况,GIN索引有一个结果集大小 “软上限” 的配置参数gin_fuzzy_search_limit,其缺省值为0,表示没有限制。如果设置了非零值,那么返回的结果就是从完整结果集中随机选择的一部分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/738177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

汽车软件市场迅猛扩张,Perforce Helix Core与Helix IPLM助力汽车软件开发的版本控制及IP生命周期管理

汽车软件世界正处于持续变革和转型之中。从自动驾驶汽车到电动汽车和先进的驾驶辅助系统&#xff0c;汽车软件的集成度和复杂性不断提升。 据美国电气与电子工程师协会的研究&#xff0c;如今大多数汽车都集成了超过1亿行代码&#xff0c;而仅仅十年前&#xff0c;这种水平的汽…

软件杯 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

ubuntu 18.04安装教程(详细有效)

文章目录 一、下载ubuntu 18.04镜像二、安装ubuntu1. 点击下载好的Vmware Workstation&#xff0c;点击新建虚拟机&#xff0c;选择 “自定义(高级)”&#xff0c;之后下一步。2. 默认配置&#xff0c;不需要更改&#xff0c;点击下一步。3. 选择 “安装程序光盘映像文件(iso)(…

Windows环境部署Hadoop-3.3.2和Spark3.3.2

目录 一、Windows环境部署Hadoop-3.3.2 1.CMD管理员解压Hadoop压缩包 2.配置系统环境变量 3.下载hadoop winutils文件 4.修改D:\server\hadoop-3.3.2\etc\hadoop目录下的配置文件 (1)core-site.xml (2)hdfs-site.xml (3)mapred-site.xml (4)yarn-site.xml (5)workers…

Oracle 层级查询(Hierarchical Queries)

如果一张表中的数据存在分级&#xff08;即数据间存在父子关系&#xff09;&#xff0c;利用普通SQL语句显示数据间的层级关系非常复杂&#xff0c;可能需要多次连接才能完整的展示出完成的层级关系&#xff0c;更困难的是你可能不知道数据到底有多少层。而利用Oracle的层级查询…

VSCode单机活动栏图标无法收起

如果活动栏为展开状态&#xff0c;单击活动栏图标可以正常收起&#xff0c;但无法通过再次单击打开&#xff0c;解决方案如下&#xff1a; 设置->工作台->外观&#xff1a; Activity Bar:Icon Click Behavior: 切换为默认的toggle

案例分析篇03:一篇文章搞定软考设计模式考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12601310.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

垃圾收集器

垃圾收集器 文章目录 垃圾收集器垃圾收集算法分代收集理论标记复制算法(eden区域算法)标记清除算法(老年代算法)标记整理算法垃圾收集器种类垃圾收集器之Serial&#xff08;-XX:useSerialGC -XX: useSerialOldGC&#xff09;垃圾收集器之Parallel Scavenge(-XX:useParallelGC -…

51单片机基础篇系列-点亮一个LED发光管基础知识搭建

&#x1f308;个人主页: 会编辑的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” LED发光二极管 它是半导体二极管的一种&#xff0c;可以把电能转化成光能&#xff0c;常简写为LED&#xff0c;发光二极管与普通二极管一样是由一个PN结组成&#xff0c;也具有单向…

Android中单例模式正确实现方式

1. 饿汉模式 -线程安全 在类加载时进行实例化&#xff0c; 线程安全&#xff0c;但会导致类加载时间变长。饿汉模式如果使用过多&#xff0c;可能会对App启动耗时带来不利影响。 2. 懒汉模式 -线程不安全 没有加锁&#xff0c; 因此线程不安全。 3. 两次判空 加同步锁 -线程不…

基于PHP构建的HTML5点餐系统的设计13.91

随着互联网时代的发展&#xff0c;人们的生活方式正在发生改变。传统的餐饮行业也正在发生变革。人们不再满足过去的点餐方式&#xff0c;需要更好的体验。本课题旨在结合点餐系统的技术优势&#xff0c;设计一个能够方便顾客与商家&#xff0c;并且节约人力成本以及可以很好地…

HTML5:七天学会基础动画网页11

CSS3动画 CSS3过渡的基本用法: CSS3过渡是元素从一种样式逐渐改变为另一种样式的效果。 过渡属性-transition 值与说明 transition-property 必需&#xff0c;指定CSS属性的name&#xff0c;transition效果即哪个属性发生过渡。 transition-duration 必需&#xff0c;t…

深入浅出计算机网络 day.2 概论⑥ 计算机网络体系结构

上帝疯狂杜撰世界悲情的命题 将凉薄和荒芜尽写 —— 24.3.12 内容概述 1.常见的三种计算机网络体系结构 2.计算机网路体系结构分层的必要性 3.计算机网络体系结构分层思想举例 4.计算机网络体系结构中的专用术语 一、常见的三种计算机网络体系结构 OSI参考模型 TCP/IP参…

深入理解Java中的线程安全List:CopyOnWriteArrayList原理和应用

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在Java并发编程中&#xff0c;线程安全的数据结构是至关重要的。其中&#xff0c;CopyOnWriteArrayList是一个线程安全的ArrayLis…

HBase非关系型数据库

HBase非关系型数据库 1 什么是HBase2 HBase的特点3 什么时候需要HBase4 HBase的数据模型5 HBase架构5.1 架构5.2 HBase如何列式储存 6 如何正确设计RowKey 1 什么是HBase HBase – Hadoop Database&#xff0c;是一个高可靠性、高性能、面向列、可伸缩、 实时读写的分布式数据…

第100+1步 ChatGPT文献复现:ARIMAX预测肺结核 vol. 1

基于WIN10的64位系统演示 一、写在前面 各位大佬&#xff0c;好久不见。 《100步入门机器学习》肝完了&#xff0c;不懂大家学了多少了&#xff0c;默认你们都学完了吧。 今年我们换一个玩法&#xff08;灌水&#xff09;&#xff1a;一系列更接近实战的教程&#xff0c;复…

(黑马出品_07)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

&#xff08;黑马出品_07&#xff09;SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术分布式搜索 今日目标1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.2.1.Bucket聚合语法1.2.2.聚合结果排序1.2.3.限定聚合范围1.2.4.Metric聚合语法1.2.5.小…

6.S081的Lab学习——Lab1: Xv6 and Unix utilities

文章目录 前言一、启动xv6(难度&#xff1a;Easy)解析&#xff1a; 二、sleep(难度&#xff1a;Easy)解析&#xff1a; 三、pingpong&#xff08;难度&#xff1a;Easy&#xff09;解析&#xff1a; 四、Primes(素数&#xff0c;难度&#xff1a;Moderate/Hard)解析&#xff1a…

node.js入门

一、cmd常用命令 windowsR 再输入cmd 打开命令提示符 (1)切换盘符 c: / d: (2)dir 查看全部内容 (3)cd 你需要打开的文件所处的大文件夹名字 (4)cd 大文件夹名再输入dir 查看该大文件里面的所有内容 (5)输出结果里 一个.表示当前目录&#xff0c;两个..表示上级目录 …

怎样在CSDN赚点零花钱

请教一下各位大佬&#xff0c;看到你们在CSDN很多都几万粉丝以上&#xff0c;能不能分享一下有什么涨粉的经验&#xff0c;还有怎样转化为额外收益……感谢各位提供宝贵的经验&#xff0c;谢谢……