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 分区

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

相关推荐
huangdong_11 分钟前
京东商品图片视频批量下载与m3u8视频合并技术完整实现方案
大数据·前端·数据库
倒流时光三十年20 分钟前
PostgreSQL CASE 条件表达式详解
数据库·postgresql
字节跳动数据平台42 分钟前
营销视频进入工业化时代,火山引擎多模态数据湖如何助力多米实现内容生产提效 100+ 倍
数据库
健康平安的活着1 小时前
mysql中数据库脚本太大,通过脚本命令修改db名称
数据库·mysql
倒流时光三十年1 小时前
PostgreSQL COALESCE 条件表达式函数详解
数据库·postgresql
让我上个超影吧2 小时前
Claude code:Hooks
java·数据库·ai编程
RH2312112 小时前
2026.6.8Linux
java·数据库·中间件
其实防守也摸鱼2 小时前
软件安全与漏洞--Windows底层原理与软件逆向工程基础
linux·网络·数据库·算法·安全·安全架构·软件安全与漏洞
minji...3 小时前
MySQL数据库 (八) MySQL表的基本查询(下),truncate、group by、聚合函数、分组聚合统计
数据库·mysql·聚合函数·update·分组聚合统计
乐世东方客3 小时前
备份脚本记录(binlog文件+mysql+mongo)
android·数据库·mysql