
问题现象相信这个报错大家肯定见过查询时报错clog不存在。postgresERROR: couldnotaccess statusoftransaction127045588DETAIL: Couldnotopenfilepg_xact/0079:Nosuch fileordirectory.postgresERROR: 58P01: couldnotaccess statusoftransaction127045588DETAIL: Couldnotopenfilepg_xact/0079:Nosuch fileordirectory.LOCATION: SlruReportIOError, slru.c:938postgres先说说危害首先报错表肯定只能操作一部分数据另外会影响到autovacuum导致实例事务id回收慢。保守或者稳妥的处理方案就是用备份恢复如果可以接受放弃一部分数据那也可以手动补齐clog解决报错。问题分析在处理这种问题时我们会去找损坏的起始位置。可以用一些函数或者数据量少时可以手动折半查找去定位。 但是耗时会比较久。DO $$DECLARErec record;BEGINFORrecinSELECTctid,*FROMtablename LOOPraise noticeParameter is: %, rec.ctid;raise noticeParameter is: %, rec;ENDLOOP;END;$$LANGUAGE plpgsql;怎么快速定位到报错的ctid呢给报错函数SlruReportIOError设置断点 进入HeapTupleSatisfiesVisibility函数tup-t_self {ip_blkid {bi_hi 48, bi_lo 53101}, ip_posid 2}那么48 16 | 53101 为3198829ip_blkid是BlockId由高16位和低16位组成因此这里将48左移16位再或运算53101就能得出当前页面的blockid。ip_posid是在当前页面中的offset所以当前tuple的ctid为(3198829,2)。ctid(3198829,2)这条元组是否可见和事务id 127045588的关系进入HeapTupleSatisfiesMVCC函数HeapTupleHeaderXminCommitted(tuple)返回false即tuple-t_infomask HEAP_XMIN_COMMITTED为0也就是状态不是committedtuple-t_infomask HEAP_MOVED_OFF为0tuple-t_infomask HEAP_MOVED_IN为0说明这条不在vacuum full操作中TransactionIdIsCurrentTransactionId(m.163.com/news/rec/YDJ0127U6U17UVYX.html(tuple)))为false说明tuple -t_choice.t_heap.t_xmin即127045588不是当前事务XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot)为false说明127045588不在活跃snapshot中可以看到snapshot的xmin 984291081, xmax 984291081xip 0x0。那么可以看到判断一个元组是否可见是先通过t_infomask标志位进行的若元组对应infomask无committed标志并且未进行vacuum full也非当前事务不在活跃事务快照中。说明当前tuple对应的xmin是一个过去的事务且可见性未知那么就需要查看clog来判断可见性。事务id 127045588和pg_xact/0079的关系TransactionIdDidCommit函数中TransactionLogFetch(m.163.com/news/rec/YDJ1037U6U17VRXX.html)通过transactionId来匹配clog文件并读取内容。经过计算127045588对应的clog为0079127045588/8192/4/32转16进制clog文件大小为256K也就是32个block(8192K)构成一个文件。那么clog文件名为xid/(BLCKSZ * CLOG_XACTS_PER_BYTE)/32 xid/(8192 * 4)/32这个文件确实当前不存在。clog保留策略是怎样的clog的清理是在vacuum/autovacuum的时候进行的大体的过程如下┌─────────────────┐ │ do_autovacuum │ │ / │ │ vacuum │ └─────────┬───────┘ │ ▼ ┌─────────────────┐ │ vac_update_ │ │ datfrozenxid │ └─────────┬───────┘ │ ▼ ┌─────────────────┐ │ vac_truncate_ │ │ clog │ └─────────┬───────┘ │ ▼ ┌─────────────────┐ │ TruncateCLOG │ └─────────┬───────┘ │ ▼ ┌─────────────────┐ │ SimpleLruTruncate│ └─────────┬───────┘ │ ▼ ┌─────────────────┐ │ SlruScanDirectory│ └─────────┬───────┘ │ ▼ ┌─────────────────┐ ┌─────────────────────────┐ │ (callback) │───▶│ SlruScanDirCbDeleteCutoff│ │ SlruScanDirCb │ └─────────┬───────────────┘ │ DeleteCutoff │ │ └─────────────────┘ ▼ ┌─────────────────────────┐ │ SlruInternalDeleteSegment│ └─────────┬───────────────┘ │ ▼ ┌─────────────────┐ │ unlink │ │ (删除文件) │ └─────────────────┘TruncateCLOG(frozenXID, oldestxid_datoid)这个函数可以看做vacuum清理clog的入口会根据frozenXID和oldestxid_datoid 即目前实例所有库中最老的xiddatfrozenxid来进行清理计算出该xid所在的clog将之前的clog使用unlink删除。当前实例frozenXID 335599317postgres# SELECT min(m.163.com/news/rec/YDJ0737U6U181AXX.html::int) FROM pg_database;min-----------335599317(1 row)postgres#经过计算开始保留的clog文件为0140所以在这之前的文件都会被清理。(gdb) p/x335599317 / 8192 / 4 / 32$14 0x140(gdb)为什么会出现这样的场景可能BUG导致老版本存在类似的BUG可以bug-list中检索具体查看可能硬件故障或实例异常崩溃crash导致。处理方案如果有备份建议用备份恢复。 如果能接受部分数据丢失则可以手动补齐缺失的clog然后vacuum full table。--生产环境请慎重操作注意路径ddif/dev/zeroof$PGDATA/pg_xact/0079bs256k count1vacuum full user_info;小结本篇记录了一起PostgreSQL数据损坏案例现象是查询时报错“clog不存在”导致部分数据无法访问并影响autovacuum。通过分析报错信息定位到损坏的数据行ctid并解释了元组可见性判断逻辑事务ID与clog文件的对应关系、clog的清理机制以及可能的原因如BUG、磁盘故障等。对于处理方案请仔细斟酌慎重选择。