暂无图片
暂无图片
5
暂无图片
暂无图片
暂无图片

PostgreSQL关于唯一索引的几个细节

1.验证唯一索引的索引文件里是否可以存储多个版本的值

结论:PG唯一索引里可以存储多个版本的值。

image.png

更新后只有一个索引值,是因为没有checkpoint。对应的新值没有落盘。手动做checkpoint,新的值会写入索引文件。
image.png

2.验证唯一索引的索引文件是否可以存储重复值

结论:唯一索引的索引文件可以存储重复值

image.png

3.索引的可见性问题

PostgreSQL数据库执行计划为IndexOnlyScan时可能并没有真的不发生回表。可以根据
Heap Fetches进一步确认。Heap Fetch表明了在index-only-scan期间,需要扫描表的数据页个数。

索引是不包含可见性信息的,可见性信息存在元组 tuple 之上 (xmin、xmax),总是需要回表去确认可见性,在 9.2 之后,支持了 Index only scan,搭配 visibility map,可以无需回表确认可见性就返回数据。

VM是用BIT位标识page中是否有dead tuple的。

/* Number of bits for one heap page */ #define BITS\_PER\_HEAPBLOCK 2

VM中为表的每个文件块设置了2bits,用来标记该文件块是否存在无效元组

/* Flags for bit map */ #define VISIBILITYMAP_ALL_VISIBLE  0x01//全部可见(该page所有元组都对事务可见) #define VISIBILITYMAP_ALL_FROZEN 0x02//全部冻结

Postgresql中如果执行计划走IndexOnlyScan通常说明扫描的字段都在索引中了,意味着不必回表直接返回结果。但PG中索引页面是没有多版本信息的,表上才有,可见行也是根据vm文件或者表的隐藏列决定的。

IndexOnlyScan访问vm页面判断如果页面的可见性为VM_ALL_VISIBLE,说明页面内没有修改过的元组,不会出现dead tuple,可以直接使用索引数据(这才是真的index only scan),不必去回表读表的数据并找根据隐藏列判断可见行。

如果VM_ALL_VISIBLE为假,说明页面内修改过元组,有dead tuple,需要去扫堆页面找到可见的元组(虽然可能执行计划是index only scan,但是由于索引指向的堆元组,无法确定可见性,所以还是要去扫堆页面,是假的index only scan)。所以PostgreSQL数据库执行计划为IndexOnlyScan但有时候并没有真的不发生回表。从图中的Heap Fetches可以看到还是扫了一个数据页。

image.png

4.唯一索引的“唯一”是怎么区分的

先扫索引,索引如果有重复值,去进一步根据索引记录的的ctid找表对应的块,看这条记录在块里存储的tuple,根据隐藏列判断是否是活跃的(即不是历史的死元组)。

如下部分是截取PG官方文档(翻译版本)

image.png

_bt_check_unique是在存在唯一索引时,插入值时一个关键的函数,去做唯一约束的检查。
可以看到因为唯一索引的原因,插入前,checkUnique=UNIQUE_CHECK_YES。

image.png

这个类型中共有四种,分别是:不进行任何唯一性检查、在插入时强制唯一性、测试唯一性但不会报错、检查现有元组是否唯一。

typedef enum IndexUniqueCheck {     UNIQUE_CHECK_NO,            /* Don't do any uniqueness checking */     UNIQUE_CHECK_YES,           /* Enforce uniqueness at insertion time */     UNIQUE_CHECK_PARTIAL,       /* Test uniqueness, but no error */     UNIQUE_CHECK_EXISTING,      /* Check if existing tuple is unique */ } IndexUniqueCheck;

判断的逻辑是如下:

        if (!indexRelation->rd_index->indisunique)             checkUnique = UNIQUE_CHECK_NO;         else if (applyNoDupErr)             checkUnique = UNIQUE_CHECK_PARTIAL;         else if (indexRelation->rd_index->indimmediate)             checkUnique = UNIQUE_CHECK_YES;         else             checkUnique = UNIQUE_CHECK_PARTIAL;

根据索引视图里的状态信息做判断。

image.png

检查唯一性的函数入参的时候,会同时把索引和表的信息同时传递进去。

image.png

image.png

下边可以看到如果在索引里找到了对应相同的记录,则会用table_index_fetch_tuple_check()函数fetch表对应的page,查看page里的该条记录是否是活跃的。

image.png

表里找到了活跃的记录则违反了唯一约束。表里找不到记录。或者找到的是不活跃的,则是已经被删除的,或者是死元组,不会有冲突,直接退出约束的检查。

image.png

如下是table_index_fetch_tuple_check()函数的部分定义。

image.png
image.png
all dead表示存在活跃的相同元组,即违反了唯一约束。如果表里检查到对应的记录是活跃的,则继续往下输出,在前台产生违反约束报错。
image.png

最后修改时间:2024-10-24 10:33:03
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论