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);

}

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

}

在向 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;

insertdata.isDelete = FALSE;

/* During index build, count the to-be-inserted entry */
if (buildStats != NULL)
buildStats->nEntries++; ginPrepareEntryScan(&btree, attnum, key, category, ginstate); // 在 B-tree 中找到叶子节点
stack = ginFindLeafPage(&btree, false);
page = BufferGetPage(stack->buffer); // 如果 key 已经存在
if (btree.findItem(&btree, stack)) {
/* found pre-existing entry */
itup = (IndexTuple)PageGetItem(page, PageGetItemId(page, stack->off));
// 如果是 posting tree 结构
if (GinIsPostingTree(itup)) {
/* add entries to existing posting tree */
BlockNumber rootPostingTree = GinGetPostingTree(itup); /* release all stack */
LockBuffer(stack->buffer, GIN_UNLOCK);
freeGinBtreeStack(stack); /* insert into posting tree */
ginInsertItemPointers(ginstate->index, rootPostingTree, items, nitem, buildStats);
return;
}
// 如果是 posting list
/* modify an existing leaf entry */
itup = addItemPointersToLeafTuple(ginstate, itup, items, nitem, buildStats); insertdata.isDelete = TRUE;
} else { // 对应的 key 不存在, 需要新建一个叶子节点里的对象
/* no match, so construct a new leaf entry */
itup = buildFreshLeafTuple(ginstate, attnum, key, category, items, nitem, buildStats);
} /* Insert the new or modified leaf tuple */
insertdata.entry = itup;
ginInsertValue(&btree, stack, &insertdata, buildStats);
pfree(itup);
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. springboot多线程TaskExecutor的使用,以及使用@Async实现异步调用

    目录 @Async实现异步调用 pom.xml 启动类 定义controller 定义接口 实现类 将isDone换程CountDownLatch来判断线程是否执行完实例化CountDownLatch ...

  2. centos 8远程分发复制jdk到另一个虚拟机

    在localzly节点操作成功后可以使用远程复制命令将JDK远程复制到slave1节点之中:(此命令在localzly中操作) scp -r /usr/java root@slave1:/usr/ 配 ...

  3. jenkins 钉钉机器人插件

    官方文档: https://jenkinsci.github.io/dingtalk-plugin/guide/getting-started.html#%E6%B3%A8%E6%84%8F 注意:系 ...

  4. Redis集群Cluster

    Redis Cluster 是社区版推出的 Redis 分布式集群解决方案,主要解决 Redis 分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster 能起到很好 ...

  5. 8、mysql的内存管理及优化

    内存优化原则 1) 将尽量多的内存分配给MySQL做缓存,但要给操作系统和其他程序预留足够内存. 2) MyISAM 存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此,如果有MyISAM表,就 ...

  6. 10、zookeeper客户端curator

    curator介绍 https://blog.csdn.net/wo541075754/article/details/68067872 关于第三方客户端的小介绍 zkClient有对dubbo的一些 ...

  7. Python中那些简单又好用的特性和用法

    Python作为我的主力语言帮助我开发了许多DevOps运维自动化系统,这篇文章总结几个我在编写Python代码过程中用到的几个简单又好用的特性和用法,这些特性和用法可以帮助我们更高效地编写Pytho ...

  8. Codeforces Round 651 (Div. 2)C. Number Game(数学思维数论)

    C. Number Game 我们考虑那些状态是必胜态 我的回合时n为奇数(除1外),直接除以n则必胜 下面偶数的情况稍复杂 偶数我们能进行的操作只有除以一个奇数,需要考虑怎么把当前状态变为对手的必败 ...

  9. gitee 流水线 定时触发 不能用,不能白嫖了

    gitee 流水线 定时触发 不能用,不能白嫖了 白研究半天了,只好回去拿centos服务器 搞定时任务了

  10. 跨域! dev: 'http://192.168.40.81:9090/xxx-api/' 平台和项目 两个都要改 要不会跨域!跨域!跨域!

    跨域! dev: 'http://192.168.40.81:9090/xxx-api/' 平台和项目 两个都要改 要不会跨域!跨域!跨域!