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

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

相关推荐
爱写代码的阿森5 小时前
鸿蒙三方库 | harmony-utils之KvUtil键值型数据库操作详解
数据库·华为·harmonyos·鸿蒙·huawei
Database_Cool_5 小时前
单机 MySQL 迁移到分布式数据库方便吗?阿里云 PolarDB-X 100% MySQL 协议兼容零改造平滑迁移
数据库·分布式·mysql
姜太小白6 小时前
【MySQL】 索引优化实战:解决 WHERE 等值 + IS NULL 查询,TEXT 字段报错 1167 的完整指南
数据库·mysql
窝子面6 小时前
手搓最简前后端协作-node
javascript·数据库
吳所畏惧7 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
DFT计算杂谈7 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习
HPFBoy7 小时前
log4net 数据库存日志正确配置
数据库
liuxiaowei37 小时前
【绝版教程】新版MySQL DBA高级实战进阶班 MySQL8.0 姜承尧-腾讯数据库总监
数据库·mysql·dba
破碎的南瓜8 小时前
sqli--sql注入过关笔记(1,2,3,4,5,9)
数据库·笔记·sql