PostgreSQL 中唯一索引的工作原理

引言

在研究分区表相关解决方案时,曾聚焦于如何在包含多个子表的分区表内,实现跨分区的数据唯一性保障。这类功能有时被称为全局索引,相关领域的讨论可参考邮件线程此处。尽管其中提出的思路具备一定合理性,但对应的实现方式却引发了较多争议 ------ 该方式改变了分区索引的存储逻辑,本质是将所有分区索引合并存储,并以TableOid作为内部引用的键值。

基于此,探索一种替代方案:在不改变 PostgreSQL 原有核心机制的前提下,实现跨分区唯一性保障。而要推进这一探索,首要任务是先厘清 PostgreSQL 分区表中,唯一性机制的底层工作原理。

CREATE INDEX 语句的唯一性保障

PostgreSQL 会通过以下基本流程检查唯一性冲突:

  1. 对目标子分区表执行堆扫描(heap scan)。
  2. 将可见元组(visible tuples)存储到一个BTSpool结构(记为 spool1)中,将无效元组(dead tuples)存储到另一个BTSpool结构(记为 spool2)中。因此,这里会用到两个BTSpool结构;若表中无无效元组,或无需保障唯一性,则 spool2 可能为 NULL。BTSpool结构可理解为索引元组的集合。
  3. 若 spool1 和 spool2 存在,则对其进行排序。
  4. 排序算法内置重复检测功能:若排序后有两个相同的元组连续出现,即判定为重复;若该索引要求唯一性,则会在此处抛出错误。
  5. 若排序过程中未检测到重复,PostgreSQL 会基于 spool1 和 spool2 构建索引树。
  6. 索引创建完成后,销毁所有BTSpool结构。
  7. 上述逻辑位于src/backend/access/nbtree/nbtsort.c文件的btbuild()函数中,并由indexcmds.c文件的DefineIndex函数调用。根据活跃子分区表的数量,DefineIndex会多次调用btbuild

INSERT 与 UPDATE 语句的唯一性保障

在规划器(planner)和优化器(optimizer)阶段,PostgreSQL 已确定新数据应插入或更新至哪个子分区表:

  1. PostgreSQL 首先将堆元组(heap tuple)插入目标堆关系(heap relation)。
  2. 随后调用src/backend/access/nbtree/nbtinsert.c文件中的_bt_doinsert()函数,尝试插入与该堆元组关联的新索引元组。
  3. 若索引要求进行唯一性检查,PostgreSQL 会根据新堆元组构建扫描键(scan key),并调用_bt_check_unique()函数从堆分区表中查询是否存在匹配的现有元组。
  4. 若当前子分区中未查询到匹配的堆元组,则无冲突。
  5. 若当前子分区中查询到匹配的堆元组,则需执行以下额外检查:
  6. 若查询到的元组尚未提交(例如,另一个后端进程仍在处理该元组且未提交),当前进程会在此处等待,直至该后端进程提交或回滚。
  7. 当该后端进程提交或回滚后,当前进程会再次查询同一元组。
  8. 若该后端进程回滚,则无法查询到重复元组,因此无冲突。
  9. 若该后端进程提交,则仍可查询到重复元组,因此存在潜在冲突。
  10. 在抛出错误前,PostgreSQL 会再执行一次检查:获取当前待插入堆元组的可见性状态。这是为了覆盖一种特殊场景------当当前后端进程尝试插入或更新数据时,另一个后端进程正在执行CREATE UNIQUE INDEX CONCURRENTLY(并发创建唯一索引)操作。
  11. 检查待插入的当前元组是否能从堆关系中查询到。
  12. 若能查询到,则当前元组仍处于可见状态,必然存在冲突。
  13. 若无法查询到,则当前元组已变为不可见状态,不视为冲突。
  14. 若未检测到冲突,则继续构建索引树。

上述逻辑主要位于src/backend/access/nbtree/nbtinsert.c文件的_bt_doinsert()_bt_check_unique()函数中。

ATTACH 操作的唯一性保障

待附加(ATTACH)的表可能已定义唯一索引(或非唯一索引),也可能完全没有索引。ATTACH 操作过程中存在两种潜在情况:

  • 向分区表附加无索引的表时,PostgreSQL 会自动为该附加表创建新索引,且索引参数与原分区表保持一致。目前,该索引的创建流程遵循2.0节中定义的步骤。
  • 向分区表附加已定义唯一索引的表(且该索引的键与分区表的全局唯一索引键相同)时,PostgreSQL 不会为该附加表创建新索引,而是直接完成附加操作。
相关推荐
没刮胡子11 小时前
mysql分页SQL
数据库·sql·mysql
fatsheep洋12 小时前
sql项目总结
数据库·sql
高级测试工程师欧阳12 小时前
SQLint3 模块如何使用
数据库·python·mysql·oracle
ActionTech12 小时前
2025 年 8 月《大模型 SQL 能力排行榜》发布
数据库·sql·oracle
一心09212 小时前
SQL(window)日志在linux 下查看
linux·数据库·windows·sql·日志
博一波12 小时前
Redis AOF 持久化:银行的 “交易流水单” 管理逻辑
数据库·redis
老邓计算机毕设13 小时前
Springboot乐家流浪猫管理系统16lxw(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
每天进步一点_JL13 小时前
InnoDB 存储引擎深度解析:从 B+ 树到全文索引的底层实现
数据库
未来影子14 小时前
系统炸了?数据库单表存了七十亿条数据
数据库