PostgreSQL分区表父索引INVALID排查实战:缺少某个分区索引导致父索引INVALID

问题现象

线上巡检时发现某个分区表索引状态异常:

复制代码
\d+ parent_table

发现父表上的一个分区索引显示为 INVALID:

复制代码
parent_idx_xxx
btree (col_a, col_b)
WHERE status = 1
  AND flag = 0
INVALID

但是查看各个分区表时,却发现对应索引都是正常的。

最开始怀疑:

  • 索引损坏

  • REINDEX失败

  • 分区索引元数据异常

于是开始排查。


一、确认父索引状态

首先查看父索引在系统表中的状态:

复制代码
SELECT
    c.oid::regclass AS index_name,
    i.indisvalid,
    i.indisready,
    i.indislive
FROM pg_class c
JOIN pg_index i
    ON i.indexrelid = c.oid
WHERE c.oid =
'parent_idx_xxx'::regclass;

结果:

复制代码
index_name   | parent_idx_xxx
indisvalid   | f
indisready   | t
indislive    | t

说明:

复制代码
索引已存在
索引可见
索引已准备完成
但不被认为是有效索引

也就是说 PostgreSQL 认为这个父索引尚未完成构建。


二、检查子分区索引状态

查看所有分区索引:

复制代码
WITH parent_idx AS (
  SELECT 'parent_idx_xxx'::regclass AS idx_oid
),
parts AS (
  SELECT inhrelid AS part_table
  FROM pg_inherits
  WHERE inhparent = 'parent_table'::regclass
),
attached AS (
  SELECT i.inhrelid AS child_idx
  FROM pg_inherits i
  JOIN parent_idx p
    ON i.inhparent = p.idx_oid
)
SELECT
  p.part_table::regclass,
  ci.indexrelid::regclass,
  ix.indisvalid,
  ix.indisready,
  CASE
    WHEN a.child_idx IS NULL
      THEN 'NOT ATTACHED'
    ELSE 'ATTACHED'
  END AS attach_status
FROM parts p
LEFT JOIN pg_index ci
  ON ci.indrelid = p.part_table
LEFT JOIN pg_index ix
  ON ix.indexrelid = ci.indexrelid
LEFT JOIN attached a
  ON a.child_idx = ci.indexrelid
WHERE pg_get_indexdef(ci.indexrelid) LIKE '%col_a%'
  AND pg_get_indexdef(ci.indexrelid) LIKE '%col_b%';

结果显示:

复制代码
所有已存在的子索引:
indisvalid=t
indisready=t
ATTACHED

说明现有的子索引全部正常。


三、发现关键线索

继续检查:

复制代码
SELECT
(
  SELECT count(*)
  FROM pg_inherits
  WHERE inhparent =
  'parent_table'::regclass
) AS partition_count,

(
  SELECT count(*)
  FROM pg_inherits
  WHERE inhparent =
  'parent_idx_xxx'::regclass
) AS attached_index_count;

结果:

复制代码
partition_count      = 27
attached_index_count = 26

这里发现异常:

复制代码
分区数 ≠ 已挂载索引数

也就是说:

复制代码
至少有一个分区缺少对应索引

这也是导致父索引始终 INVALID 的真正原因。


四、定位缺失分区

执行:

复制代码
WITH parts AS (
  SELECT inhrelid AS part_table
  FROM pg_inherits
  WHERE inhparent =
  'parent_table'::regclass
),
attached_idx AS (
  SELECT ci.indrelid AS part_table
  FROM pg_inherits pi
  JOIN pg_index ci
    ON ci.indexrelid = pi.inhrelid
  WHERE pi.inhparent =
  'parent_idx_xxx'::regclass
)
SELECT
  p.part_table::regclass AS missing_partition
FROM parts p
LEFT JOIN attached_idx a
  ON a.part_table = p.part_table
WHERE a.part_table IS NULL;

结果:

复制代码
parent_table_default

找到问题分区:

复制代码
Default分区缺少对应索引

五、补建索引

查看发现 Default 分区确实没有该索引。

创建索引:

复制代码
CREATE INDEX CONCURRENTLY idx_default_xxx
ON parent_table_default (col_a, col_b)
WHERE status = 1
  AND flag = 0;

由于线上环境,建议使用:

复制代码
CREATE INDEX CONCURRENTLY

避免长时间锁表。


六、Attach到父索引

创建完成后:

复制代码
SET lock_timeout = '2s';

ALTER INDEX parent_idx_xxx
ATTACH PARTITION idx_default_xxx;

再次检查:

复制代码
SELECT
    indisvalid,
    indisready,
    indislive
FROM pg_index
WHERE indexrelid =
'parent_idx_xxx'::regclass;

结果:

复制代码
indisvalid = t
indisready = t
indislive  = t

恢复正常。


原因分析

PostgreSQL 分区索引本质上由两部分组成:

  1. 父索引(元数据定义)

  2. 各个分区上的实际索引

只有当:

复制代码
所有分区
都有对应索引
并且全部ATTACH

父索引才会被标记为:

复制代码
indisvalid = true

如果哪怕缺少一个分区索引:

复制代码
父索引就会显示 INVALID

即使其它所有分区索引全部正常。


快速排查模板

以后遇到:

复制代码
分区表父索引 INVALID

优先执行:

复制代码
SELECT
(
 SELECT count(*)
 FROM pg_inherits
 WHERE inhparent='父表'::regclass
) AS partition_count,

(
 SELECT count(*)
 FROM pg_inherits
 WHERE inhparent='父索引'::regclass
) AS attached_index_count;

如果:

复制代码
partition_count > attached_index_count

基本可以判断:

复制代码
有分区缺失对应索引

然后进一步定位:

复制代码
WITH parts AS (
  SELECT inhrelid AS part_table
  FROM pg_inherits
  WHERE inhparent = '父表'::regclass
),
attached_idx AS (
  SELECT ci.indrelid AS part_table
  FROM pg_inherits pi
  JOIN pg_index ci
    ON ci.indexrelid = pi.inhrelid
  WHERE pi.inhparent = '父索引'::regclass
)
SELECT p.part_table::regclass
FROM parts p
LEFT JOIN attached_idx a
  ON a.part_table = p.part_table
WHERE a.part_table IS NULL;

总结

本次问题最终并不是索引损坏,也不是需要 REINDEX,而是:

复制代码
Default分区缺少对应索引
↓
父索引少了一个已Attach的子索引
↓
父索引状态变为INVALID
↓
补建索引并ATTACH后恢复正常

对于 PostgreSQL 分区索引来说,看到父索引 INVALID 时,不要第一时间执行 REINDEX。

建议优先检查:

  1. 子索引是否全部存在

  2. 子索引是否全部 valid

  3. 分区数量是否等于 attached 子索引数量

  4. 是否遗漏了 Default 分区

很多情况下,问题就出在最后一条。

相关推荐
2603_965148115 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
ltl5 小时前
RocksDB 经典故障排查:L0、compaction 与 write stall
数据库
xfhuangfu6 小时前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT6 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData7 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean7 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean7 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定
浪子明X8 小时前
从 MongoDB 文档到关系模型:构建可重跑、可对账的数据迁移流水线
数据库·mongodb·oracle
杜子不疼.8 小时前
国产数据库撑起固井软件自主化:金仓 × 中海油服「海恒 Cemsol」落地解析
数据库
隔窗听雨眠8 小时前
GBase 8s并发控制深度解析:封锁机制、隔离级别与死锁处理全攻略
服务器·数据库·oracle