openGauss Gin 索引

概述

GIN(Generalized Inverted Index)通用倒排索引,是首选的文本搜索索引类型。倒排索引对应的列上的数据类型通常是一个多值类型,索引中包含每个单词的索引条目,以及所匹配的位置的压缩列表。如果搜索条件是多个单词,可以先使用第一个单词进行匹配,再在找到的结果中使用其他单词删除不匹配的项。Gin 索引的 key 是多值类型中出现的单词,叶子节点中存储了每个单词出现的 TID 的列表。如果这个 TID 列表比较小,它可以和元素放在同一个页面中(称为 posting list)。如果列表比较大,就需要用到更高效的数据结构 B-tree,这样的 B-tree 位于单独的数据页上(称为 posting tree)。

索引结构

Gin 索引大的组织结构是一棵 B-tree 如图-1 所示

其中也有 meta-page、root-page 等 page,如果一个 key 对应的 tids 比较少可以和 key 放在同一个 page 中作为叶子节点; 如果对应的 tids 比较多(占用的空间的大小),需要将这些 tids 放到单独的数据页上,并且以 B-tree 的形式组织方便快速查找,叶子节点中记录对应的 B-tree 的 root-page 的信息。

图 1 Gin 索引结构示意图

语法

CREATE INDEX name ON table USING GIN (column);

openGauss 中创建 gin 索引时,索引列的类型必须是 tsvector 类型。

Example:

postgres=# create table ts(doc text, doc_tsv tsvector);

postgres=# insert into ts(doc) values

('Can a sheet slitter slit sheets?'),

('How many sheets could a sheet slitter slit?'),

('I slit a sheet, a sheet I slit.'),

('Upon a slitted sheet I sit.'),

('Whoever slit the sheets is a good sheet slitter.'),

('I am a sheet slitter.'),

('I slit sheets.'),

('I am the sleekest sheet slitter that ever slit sheets.'),

('She slits the sheet she sits on.');

postgres=# update ts set doc_tsv = to_tsvector(doc);

postgres=# create index on ts using gin(doc_tsv);

查询一个既包含 many 又包含 slitter 的 doc 如下:

实现

Gin 索引的实现主要在 src/gausskernel/storage/access/gin 下,主要文件及功能如下:

表 1

文件

功能

ginbtree.cpp

倒排索引page处理相关函数

ginarrayproc.cpp

支持倒排索引处理各种数组类型的函数

gindatapage.cpp

倒排索引处理 posting tree page 相关实现

gininsert.cpp

倒排索引插入相关实现

ginpostinglist.cpp

倒排索引处理 posting list 相关实现

ginscan.cpp

倒排索引扫描相关实现

ginget.cpp

倒排索引scan过程中获取tuple相关实现

ginxlog.cpp

倒排索引xlog回放相关实现

ginvacuum.cpp

倒排索引delete和vacuum相关实现

查看 pg_am 中 Gin 索引相关处理函数:

amname

gin

aminsert

gininsert

ambeginscan

ginbeginscan

amendscan

ginendscan

amgetbitmap

gingetbitmap

ambuild

ginbuild

构建 Gin 索引

ginbuild

{

...

// 初始化工作,如 创建 gin 索引的 meta 和 root,即 XLOG 等

buildInitialize(index, &buildstate);

// scan heap tuples 调用 ginBuildCallback 处理每个要加入索引的 tuple

// ginBuildCallback 会从 heap tuple 中提取 entries,如果有多个值

// 会对这些值进行去重和排序。得到去重及排完序的 entries 后,调用 ginInsertBAEntries

// 将这些 entries 及 对应的 tids 插入一棵RB-tree

reltuples = tableam_index_build_scan(heap, index, indexInfo, false, ginBuildCallback, (void)&buildstate);

...

// 从RB-tree中把之前插入的 entries 和 tids scan 出来,插入到 gin index 中

while ((list = ginGetBAEntry(&buildstate.accum, &attnum, &key, &category, &nlist)) != NULL) {

/
there could be many entries, so be willing to abort here */

CHECK_FOR_INTERRUPTS();

// 如果 key 不存在,则新增一个 key entry,如果已经存在则更新对应的 tids

// 首先在gin索引中查找到对应 key 的叶子节点,如果 key 已经存在,更新对应的 tids

// 不存在则插入一个新的叶子节点

ginEntryInsert(&buildstate.ginstate, attnum, key, category, list, nlist, &buildstate.buildStats);

}

  1. ...
  2. // 更新 meta-page 中的信息, 记 XLOG
  3. ginUpdateStats(index, &buildstate.buildStats);
  4. ...
  5. 返回结果

}

在向 gin 索引中插入数据时,首先和 B-tree 索引一样,首先需要查找对应的 key 是否存在;

如果 key 已经存在,则查看现在叶子节点中 key 对应的 tids 是 posting tree 还是 posting list,更新 tids;

posting list 如果由于更新导致 tids 比较多,可能变为 posting tree

如果 key 不存在,则在叶子节点中插入这个新的 key 以及对应的 tids。

void ginEntryInsert(GinState *ginstate, OffsetNumber attnum, Datum key, GinNullCategory category,

ItemPointerData *items, uint32 nitem, GinStatsData *buildStats)

{

GinBtreeData btree;

GinBtreeEntryInsertData insertdata;

GinBtreeStack *stack = NULL;

IndexTuple itup;

Page page;

  1. insertdata.isDelete = FALSE;
  2. /* During index build, count the to-be-inserted entry */
  3. if (buildStats != NULL)
  4. buildStats->nEntries++;
  5. ginPrepareEntryScan(&btree, attnum, key, category, ginstate);
  6. // 在 B-tree 中找到叶子节点
  7. stack = ginFindLeafPage(&btree, false);
  8. page = BufferGetPage(stack->buffer);
  9. // 如果 key 已经存在
  10. if (btree.findItem(&btree, stack)) {
  11. /* found pre-existing entry */
  12. itup = (IndexTuple)PageGetItem(page, PageGetItemId(page, stack->off));
  13. // 如果是 posting tree 结构
  14. if (GinIsPostingTree(itup)) {
  15. /* add entries to existing posting tree */
  16. BlockNumber rootPostingTree = GinGetPostingTree(itup);
  17. /* release all stack */
  18. LockBuffer(stack->buffer, GIN_UNLOCK);
  19. freeGinBtreeStack(stack);
  20. /* insert into posting tree */
  21. ginInsertItemPointers(ginstate->index, rootPostingTree, items, nitem, buildStats);
  22. return;
  23. }
  24. // 如果是 posting list
  25. /* modify an existing leaf entry */
  26. itup = addItemPointersToLeafTuple(ginstate, itup, items, nitem, buildStats);
  27. insertdata.isDelete = TRUE;
  28. } else { // 对应的 key 不存在, 需要新建一个叶子节点里的对象
  29. /* no match, so construct a new leaf entry */
  30. itup = buildFreshLeafTuple(ginstate, attnum, key, category, items, nitem, buildStats);
  31. }
  32. /* Insert the new or modified leaf tuple */
  33. insertdata.entry = itup;
  34. ginInsertValue(&btree, stack, &insertdata, buildStats);
  35. pfree(itup);
  36. itup = NULL;

}

gin 的 B-tree 也会涉及分裂等问题,和 B-tree 的分裂类似,因此在使用过程中也会有与 B-tree 索引使用过程中 moveright 类似的动作,本文不展开介绍分裂相关内容了。

相关数据结构:

// 用于表示一个 key 及 与其关联的 tids 的数据结构

typedef struct GinEntryAccumulator {

RBNode rbnode;

Datum key;

GinNullCategory category;

OffsetNumber attnum;

bool shouldSort;

ItemPointerData list;

uint32 maxcount; /
allocated size of list[] /

uint32 count; /
current number of list[] entries */

} GinEntryAccumulator;

// Gin 索引整体结构为 B-tree 结构

// B-tree 中的一个节点

typedef struct GinBtreeStack {

BlockNumber blkno;

Buffer buffer;

OffsetNumber off;

ItemPointerData iptr;

/* predictNumber contains predicted number of pages on current level */

uint32 predictNumber;

struct GinBtreeStack *parent; // 父节点

} GinBtreeStack;

typedef struct GinBtreeData *GinBtree;

gin 索引的查找和插入的流程在构建 gin 索引的流程中都有涉及,和 B-tree 有些类似,本文不展开介绍了。

另外需要注意的一点是,gin 索引是行存表和列存表都支持的索引类型,但是在 pg_am 中行存表的 gin 和 列存表的 gin 是两条记录,cgin pg_am 中相关处理函数如下所示:

表 2

amname

cgin

aminsert

gininsert

ambeginscan

ginbeginscan

amendscan

ginendscan

amgetbitmap

cgingetbitmap

ambuild

cginbuild

可以看出列存表的 gin 索引大部分处理函数和行存表是共用的,但索引构建的实现和行存不同,主要差异点是行存表和列存表底层存储及访问方式的差异,gin 索引本身的实现并没有太大差别。

索引删除和 vacuum 相关的内容不在本文讨论,这块内容后面单独叙述。

openGauss Gin 索引的更多相关文章

  1. 浅谈postgresql的GIN索引(通用倒排索引)

    1.倒排索引原理 倒排索引来源于搜索引擎的技术,可以说是搜索引擎的基石.正是有了倒排索引技术,搜索引擎才能有效率的进行数据库查找.删除等操作.在详细说明倒排索引之前,我们说一下与之相关的正排索引并与之 ...

  2. postgresql 创建gin索引

    1.创建gin类型的索引 postgresql 创建gin索引遇到的问题:1.ERROR: operator class "gin_trgm_ops" does not exist ...

  3. gin索引优化实例1

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

  4. postgresql gin索引使用

    由于属于老项目,postgresql使用版本9.6,主要解决‘%name%"查询无法使用索引问题.pg_trgm模块提供函数和操作符测定字母,数字,文本基于三元模型匹配的相似性, 还有支持快 ...

  5. GIN 索引

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

  6. GIN and RUM 索引性能比较

    gin索引字段entry构造的TREE,在末端posting tree|list 里面存储的是entry对应的行号. 别无其他信息.rum索引,与GIN类似,但是在posting list|tree的 ...

  7. psql-09表:视图和索引

    视图 由查询语句定义的虚拟表;从视图中看到的数据可能来自数据库中的一张或多张表,也可能来自外部; 使用视图的原因一般有: 使复制的查询易于理解和使用; 安全原因; 表一些函数返回的结果映射成视图; 一 ...

  8. PostgreSQL自学笔记:9 索引

    9 索引 9.1 索引简介 索引是对数据库表中一列或多列值进行排序的一种结构,使用 索引可提高数据库中特定数据的查询速度 9.1.1 索引的含义和特点 索引是一种单独的.存储在磁盘上的数据库结构,他们 ...

  9. PostgreSQL索引介绍

    h1, h2, h3, h4, h5, h6, p, blockquote { margin: 5px; padding: 5; } body { font-family: "Helveti ...

  10. postgres 索引

    索引是一种特殊的查询表,可以使用搜索引擎的数据库以加快数据检索.简单地说,索引是表中的数据的一个指针,在一个数据库中的索引是非常相似,如:一本书的目录. 例如,如果想在一本书中引用的所有页面讨论某个话 ...

随机推荐

  1. 【Azure Redis 缓存】Redis连接无法建立问题的排查(注:Azure Redis集成在VNET中)

    问题描述 在Azure App Service中部署的应用,需要连接到Redis中,目标Redis已经集成了虚拟网络(VNET)并且在Redis的网络防火墙中已经添加App Service的出站IP地 ...

  2. 【Azure 应用服务】App Service For Linux 怎么安装Composer,怎么安装PHP扩展,怎么来修改站点根路径启动程序?

    问题一:App Service 的默认启动路径为wwwroot,如何修改到到PHP代码运行目录呢? 如Laravel的启动目录为public/?那如何修改呢? App Service 的默认 PHP ...

  3. Java 异常处理(2) : 方法重写的规则之一:

    1 package com.bytezero.throwable; 2 3 import java.io.FileNotFoundException; 4 import java.io.IOExcep ...

  4. 油猴脚本 - dicts.cn 单词自动跳转 双核浏览器可用

    跳转格式 http://www.dicts.cn/?w=blight 20230605 更新 // ==UserScript== // @name dicts.cn 单词自动跳转 双核浏览器可用 // ...

  5. python queue模块实例解析

    一 概念: 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,是一种操作受限制的线性表. 进行插入操作的端称为队尾,进行 ...

  6. 基于BES2500芯片的低功耗蓝牙BLE游戏手柄解决方案源码解析

    一 往事    寒冬腊月,在一个寂静的天空飘着碎银雪花的夜晚.我接到这么一个电话:"朋友,能否帮忙开发一个游戏手柄的案子?我们遇到了一些问题,迟迟无法解决.",喔,这边我陷入了沉思 ...

  7. FFmpeg 基本操作摘要(一) (转流、解码、编码)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  8. FreeRTOS教程6 互斥量

    1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP仿真器 XCO ...

  9. SpringBoot如何优雅的进行参数校验

    写在前面 上一篇文章中我们学会了如何优雅的接收前端参数,传送门 SpringBoot如何优雅的接收前端参数 接收到参数后,接下来要做的就是校验参数的合法性.这一步的重要性就不用多说了. 即使前端已经对 ...

  10. Python数据库编程全指南SQLite和MySQL实践

    本文分享自华为云社区<Python数据库编程全指南SQLite和MySQL实践>,作者: 柠檬味拥抱. 1. 安装必要的库 首先,我们需要安装Python的数据库驱动程序,以便与SQLit ...