DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

PostgreSQL 并发唯一索引显示 INVALID,却仍拒绝重复写入

并发唯一索引构建失败后,INVALID索引仍可能检查写入唯一性。先定位错误对象与构建阶段,保留完整性保护,再安排针对索引的并发重建。

CREATE UNIQUE INDEX CONCURRENTLY 已经失败,psql 显示索引为 INVALID,之后插入同一组键仍收到 duplicate key value violates unique constraint。查询计划不用这个索引,却还有写入冲突,两种表现可以同时成立。

并发唯一索引开始第二次表扫描时,唯一性检查已经对其他事务生效。如果在这一阶段失败,留下的无效索引仍可能继续执行唯一性检查。INVALID 表示不能安全地用于查询,不等于已经从写入路径移除。

以下按 PostgreSQL 18 说明,限定普通非分区业务表上失败的并发唯一索引构建。不讨论系统目录、TOAST、排除约束索引或仍在执行的构建;这些对象和阶段需要另外核对。

先确认报错来自哪一个索引

保留建索引失败的完整日志及后续写入错误,尤其是错误中命名的约束或索引。表上已有的主键、其他唯一约束也能拒绝写入,不能仅凭看到一个 INVALID 就认定它是原因。

在同一数据库检查目标索引的状态和定义。示例名称 public.events_external_id_uq 需要换为错误中确认的实际索引:

SELECT indexrelid::regclass AS index_name,
       indrelid::regclass AS table_name,
       indisunique,
       indisvalid,
       indisready,
       indislive,
       pg_get_indexdef(indexrelid) AS definition
FROM pg_index
WHERE indexrelid = to_regclass('public.events_external_id_uq');

indisunique 表示唯一索引,indisvalid 表示对查询有效,indisready 表示已经准备好接受插入更新。indisready = true、indisvalid = false 是需要区分的状态;不能只查后一个字段。没有返回行则先核对名称与数据库,indislive = false 还涉及正在删除的状态。

这些字段本身不能证明某次错误一定由它产生,仍应对照错误对象名、索引键、部分索引条件及实际写入值。尤其是表达式唯一索引,比较的可能是 lower(...) 等计算结果,而非原始文本。

无效不代表已有数据都符合唯一规则

第二次扫描失败后继续拒绝部分重复写入,并不能证明整张表已经没有重复。索引可能不完整,也不能用于查询,因此“新插入被拒绝”与“历史数据全部符合唯一性”是两项不同检查。

查看最初失败原因:是已有或并发出现的重复键,还是死锁、表达式求值失败等其他问题。按真实索引定义检查数据;普通 GROUP BY external_id 不能替代表达式或部分索引的键与范围,也不能忽略 NULL 的比较规则。涉及空值时,可先核对唯一约束中的 NULL 规则。

不要修改 pg_index 的标志让它看起来有效。那不会补齐遗漏的索引内容。也不要为了使业务插入成功直接删除该唯一索引:删除可能移除当前仍在执行的完整性保护,使新的重复继续进入。

修复失败原因后,针对索引安排重建

确认原构建已经结束,保留数据、索引定义及依赖关系,并按业务规则处理导致构建失败的条件。需要合并或删除数据时,先确定关联与保留依据,不用固定“留最小 ID”替业务决定。持续写入的系统还要协调写入规则,避免检查后又产生新冲突。

对符合本文范围的无效索引,可以评估 REINDEX INDEX CONCURRENTLY,针对这个索引重建。并发重建无效索引要使用 INDEX 形式;不能把它换成 REINDEX TABLE CONCURRENTLY 并假定同样修好该对象。此操作不能放进事务块,也需要相应维护权限和资源预算。

并发方式会增加扫描、存储、CPU 和 I/O 工作量,可能等待已有事务,不能理解为即时完成或没有运行影响。重建若仍遇到重复,应该继续检查数据与并发写入;不要把失败标记为已修复,更不要反复增加超时掩盖数据问题。

如果迁移工具无法把这项操作放在事务之外,应核对并发索引与迁移事务的边界。那解决的是语句能否启动,与本页已进入构建阶段后留下的唯一索引状态不同。

同时核对读取、写入和重建残留

重建完成后重新查询状态,确认预期索引定义保持不变,indisvalid 与 indisready 为真,并检查日志及该表全部索引清单。并发重建中途失败可能留下额外无效索引;不能仅凭 _ccnew 或 _ccold 名称就删除对象,应结合本次操作记录及实际状态制定清理步骤。

业务验收需要确认合法不同键能正常写入、预期重复仍被拒绝,以及原查询正常。是否选择该索引由执行计划、统计信息与条件决定,“已有效”不保证每次查询都用它。

回退计划也要保留原唯一性要求。重建不会恢复此前单独合并或删除的数据;这些数据处理需有自己的恢复记录。若计划放弃该唯一规则,应作为独立的业务模型变更审定,不能当成一次无效索引清理顺手完成。