PostgreSQL 索引损坏问题排查以及修复
此处仅记录最近遇到的一次索引损坏的排查流程,诸位可以参考
起因:
在日志中发现了报错
org.postgresql.util.PSQLException ERROR
DATA MAY BECOME OUT OF SYNC: UPDATE table_auc SET start_price = 0 WHERE auc_id = 660;
ERROR: table tid from new index tuple (2213537,1) overlaps with invalid duplicate tuple at offset 67 of block 97335 in index "inactive_idx_name"org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2468)org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2211)org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:309)org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:446)org.postgresql.jdbc.PgStatement.execute(PgStatement.java:370)org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:311)org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:297)org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:274)org.postgresql.jdbc.PgStatement.executeUpdate(PgStatement.java:246)lib_db.DbConnectPostgresql.update(DbConnectPostgresql.java:146)lib_db.DbConnectPostgresql.update(DbConnectPostgresql.java:194)lib_db.table.Table.update(Table.java:47)lib_generic.object.GenericObjectTable.updateValue(GenericObjectTable.java:160)由上面的日志可以知道,索引结构出现了逻辑冲突,也就是 索引页(block)中有重复或无效的 tuple。这通常和系统崩溃、硬盘 I/O 问题或旧版本 PostgreSQL 的 bug 有关。
确认问题
用 PostgreSQL 自带的 amcheck 扩展检查索引是否真的坏了:
CREATE EXTENSION IF NOT EXISTS amcheck;-- 检查索引结构(轻量级)
SELECT bt_index_check('idx_name', true);-- 深度检查父子页、排序等一致性
SELECT bt_index_parent_check('idx_name', true, true);有输出的话就是有问题
修复
REINDEX INDEX CONCURRENTLY idx_name;出现的原因排查
简单分析了可能的原因,有一下几种情况
- 数据库或系统 非正常关机; 
- 磁盘坏块 / 文件系统错误; 
- PostgreSQL 旧版本的 btree bug; 
- 或者 硬件(RAID 控制器 / SSD)异常。 
- 排序规则版本不匹配 
一般我们使用排除法,这里我排查了以上所有的情况,最后 判定为是 排序规则版本不匹配。
上面的排除方式就不细讲了,这里只写怎么确认是排序规则不匹配的
排序规则版本不匹配
升级 PostgreSQL/操作系统(glibc/ICU)后,文本类 B-tree 索引的排序顺序可能与新版本库不一致,会被要求 REINDEX 并刷新排序规则版本。
由于近期迁移了系统,所以首先想到的是环境变化
新旧环境对比
# 确认这个库到底走 glibc 还是 ICU
SELECT datname, datlocprovider, datcollate, datctype
FROM pg_database
WHERE datname = current_database();# Glibc版本
ldd --version
| 环境 | PostgreSQL | glibc 版本 | datlocprovider | 排序规则 | 
|---|---|---|---|---|
| 旧机 | ≤ 14 | 2.26 | (字段不存在,隐式 glibc) | en_US.UTF-8 | 
| 新机 | 16 | 2.34 | c (glibc) | en_US.UTF-8 | 
这样的话,能确认都是 使用的GLIBC,但是版本不同
在旧机器环境下执行排序(version:2.26)
dynadot=# WITH vals AS (SELECT * FROM (VALUES ('009-1tv.com'), ('0091tv.com')) v(s)
),
c_sorted AS (SELECT 'C' AS coll, s,row_number() OVER (ORDER BY s COLLATE "C") AS ordFROM vals
),
default_sorted AS (SELECT 'default' AS coll, s,row_number() OVER (ORDER BY s COLLATE "default") AS ordFROM vals
)
SELECT coll, s
FROM (SELECT * FROM c_sortedUNION ALLSELECT * FROM default_sorted
) u
ORDER BY coll, ord;coll   |      s      
---------+-------------C       | 009-1C       | 0091default | 009-1default | 0091
(4 rows)
在新机器环境下执行排序(version:2.34)
dynadot=# WITH vals AS (SELECT * FROM (VALUES ('009-1tv.com'), ('0091tv.com')) v(s)
),
c_sorted AS (SELECT 'C' AS coll, s,row_number() OVER (ORDER BY s COLLATE "C") AS ordFROM vals
),
default_sorted AS (SELECT 'default' AS coll, s,row_number() OVER (ORDER BY s COLLATE "default") AS ordFROM vals
)
SELECT coll, s
FROM (SELECT * FROM c_sortedUNION ALLSELECT * FROM default_sorted
) u
ORDER BY coll, ord;coll   |      s      
---------+-------------C       | 009-1C       | 0091default | 0091default | 009-1
(4 rows)
由此可见,我们使用的default的排序规则,新旧环境是不一样的
glibc由2.26升级到2.34后,排序规则有所变更。
“1”与“-”的权重发生了变化
结论
新系统中,pg使用glibc2.34版本去做排序,但是索引是使用2.26的排序规则生成的,所以就会报索引损坏。
解决方案
对有问题的索引执行reindex即可。
