PostgreSQL 冻结(Freeze)机制深度解析
PostgreSQL 冻结Freeze机制深度解析一、为什么需要冻结1.1 事务 ID 的本质PostgreSQL 用 32 位无符号整数表示事务 IDXID范围 0 ~ 2^32-1约 42 亿。其中有三个特殊 XIDXID 值名称含义0InvalidTransactionId无效事务1BootstrapTransactionId初始化事务2FrozenTransactionId冻结事务永远可见正常分配从 XID3 开始。1.2 XID 比较的模运算陷阱PostgreSQL 判断事务可见性使用模 2^31 的有符号比较XID A 在 XID B 之前older(B - A) mod 2^32 2^31这意味着每个 XID 只有前 2^31 个是过去后 2^31 个是未来。回绕场景时间轴… XID1亿 XID20亿 XID40亿 XID1回绕XID2亿 …↑XID 耗尽后从头分配此时 XID1亿 的旧数据对 XID2亿 的新事务来说变成了未来→ 旧数据对所有人不可见相当于数据消失1.3 冻结的本质将元组的t_xmin或t_xmax替换为FrozenTransactionId 2该值在模运算中永远被认为比任何正常 XID 更老即对所有事务永远可见彻底脱离 XID 比较逻辑。二、冻结的两种实现方式2.1 旧方式物理写入 FrozenXIDPG 9.4 之前直接将 tuple header 中的t_xmin改写为FrozenTransactionId 2。缺点每次冻结都要修改 tuple产生大量 WALI/O 开销大。2.2 新方式infomask 标志位PG 9.4当前主流不修改t_xmin的值而是在 tuple 的t_infomask中设置两个标志位标志位含义HEAP_XMIN_COMMITTEDxmin 事务已提交HEAP_XMIN_FROZENxmin 已冻结永远可见当HEAP_XMIN_FROZEN被设置时可见性检查直接返回可见无需查pg_xact原pg_clog。优点减少 WAL 量只需记录 infomask 变更而非 xmin 值变更批量冻结整页时可以只更新 VM不修改每个 tuple三、冻结触发条件3.1 元组级冻结条件当前 XID - tuple.t_xmin vacuum_freeze_min_age默认 50,000,000满足条件的 tuple 在 VACUUM 扫描时被冻结。vacuum_freeze_min_age设置较大保留旧版本更长时间减少不必要冻结设置较小更激进冻结降低回绕风险但增加 I/O。3.2 表级全量扫描冻结条件当前 XID - pg_class.relfrozenxid vacuum_freeze_table_age默认 150,000,000触发后 VACUUM 忽略 Visibility Map强制扫描所有页包括全可见页确保所有旧 tuple 都被冻结。3.3 强制 AutovacuumAggressive Vacuum当前 XID - pg_class.relfrozenxid autovacuum_freeze_max_age默认 200,000,000即使autovacuum offPostgreSQL 也会强制启动 autovacuum worker对该表执行冻结。这是最后一道防线。3.4 三个阈值的关系|–freeze_min_age(50M)–|–freeze_table_age(150M)–|–freeze_max_age(200M)–|↑ ↑ ↑开始全表扫描冻结 强制autovacuum 数据库关闭保护relfrozenxid 距回绕 1000万 XID 时PostgreSQL 在距回绕还剩约1000 万 XID时会拒绝新事务只允许 superuser 连接执行 VACUUM距回绕还剩约100 万 XID时数据库会 PANIC 关闭。四、relfrozenxid 与 datfrozenxid4.1 relfrozenxid存储在pg_class.relfrozenxid表示该表中所有 XID 小于此值的 tuple 都已被冻结。VACUUM 完成后更新此值为min(当前 XID - vacuum_freeze_min_age, 本次扫描中最老的未冻结 xmin)。4.2 datfrozenxid存储在pg_database.datfrozenxid是该数据库内所有表的 relfrozenxid 的最小值。系统级回绕风险以datfrozenxid为准。4.3 查询冻结状态sql– 数据库级别 XID 年龄SELECTdatname,age(datfrozenxid) AS xid_age,2000000000 - age(datfrozenxid) AS xid_remaining,datfrozenxidFROM pg_databaseORDER BY xid_age DESC;– 表级别 XID 年龄找出最危险的表SELECTn.nspname AS schema,c.relname AS table_name,age(c.relfrozenxid) AS xid_age,c.relfrozenxidFROM pg_class cJOIN pg_namespace n ON c.relnamespace n.oidWHERE c.relkind ‘r’ORDER BY xid_age DESCLIMIT 20;五、页级冻结优化PG 145.1 整页冻结Page-level FreezePG 14 引入了更激进的整页冻结策略当一个页内所有 tuple 都满足冻结条件时直接在 VM 中设置ALL_FROZENbit后续 VACUUM 完全跳过该页大幅减少重复扫描开销。5.2 冻结与 VM 的协作VACUUM 扫描某页│├─ 页内所有 tuple 均已冻结│ YES → 设置 VM.ALL_FROZEN bit│ → 后续 VACUUM 跳过此页无需再扫描│└─ 页内所有 tuple 对所有事务可见无死元组YES → 设置 VM.ALL_VISIBLE bit→ Index-Only Scan 可跳过回表六、MultiXact 冻结6.1 什么是 MultiXact当多个事务同时对同一行加行锁SELECT FOR SHARE等PostgreSQL 用MultiXactId替代单个 XID 存储在t_xmax中。MultiXactId 同样是 32 位整数同样面临回绕问题。6.2 MultiXact 冻结参数参数默认值说明vacuum_multixact_freeze_min_age5,000,000MultiXact 冻结的最小年龄vacuum_multixact_freeze_table_age150,000,000触发全表扫描的 MultiXact 年龄autovacuum_multixact_freeze_max_age400,000,000强制 autovacuum 的 MultiXact 年龄七、冻结相关操作7.1 手动触发冻结-- 对指定表执行冻结扫描所有页VACUUM FREEZE mytable;-- 带详细输出VACUUM FREEZE VERBOSE mytable;-- 对整个数据库执行冻结VACUUM FREEZE;7.2 观察冻结进度-- 查看 VACUUM FREEZE 进度SELECTpid,datname,relid::regclassAStable_name,phase,heap_blks_total,heap_blks_scanned,heap_blks_vacuumed,num_dead_tuples,num_frozen_tuples-- PG 16 新增字段FROMpg_stat_progress_vacuum;7.3 用 pageinspect 验证冻结状态CREATEEXTENSION pageinspect;-- 查看 tuple 的冻结标志SELECTlp,t_xmin,t_xmax,-- HEAP_XMIN_FROZEN 0x0200 (infomask bit)(t_infomask512)0ASxmin_frozen,(t_infomask256)0ASxmin_committedFROMheap_page_items(get_raw_page(mytable,0));八、冻结失败的常见原因原因说明解决方法长事务长事务持有的快照阻止旧 XID 被冻结监控并终止长事务长 Replication Slot备库 slot 持有的xmin阻止冻结推进清理不用的 slotvacuum_defer_cleanup_age延迟清理导致冻结推迟评估是否需要此参数表被锁VACUUM 无法获取锁排查锁等待检查阻塞冻结的 Replication SlotSELECTslot_name,slot_type,active,age(xmin)ASxmin_age,age(catalog_xmin)AScatalog_xmin_age,restart_lsnFROMpg_replication_slotsORDERBYage(xmin)DESCNULLSLAST;九、总结问题根源XID 是 32 位整数约 42 亿后回绕旧数据变未来数据 → 不可见解决方案冻结Freeze将 tuple 标记为 HEAP_XMIN_FROZEN→ 永远可见脱离 XID 比较触发层次由宽松到严格元组级xmin 年龄 vacuum_freeze_min_age50M→ 单 tuple 冻结表级relfrozenxid 年龄 vacuum_freeze_table_age150M→ 全表扫描冻结强制relfrozenxid 年龄 autovacuum_freeze_max_age200M→ 强制 autovacuum紧急距回绕 1000万 XID → 拒绝新事务崩溃距回绕 100万 XID → 数据库 PANIC日常运维要点✓ 监控 age(datfrozenxid) 和 age(relfrozenxid)保持远低于 200M✓ 确保 autovacuum 正常运行不被长事务/长 slot 阻塞✓ 对写入频繁的大表设置更激进的 autovacuum 参数✓ 定期检查 pg_replication_slots清理废弃 slot✓ 生产环境 XID 年龄超过 150M 时应立即人工介入执行 VACUUM FREEZEXID 分配是全局的集群级别XID 计数器是整个 PostgreSQL 实例cluster共享的所有数据库的所有事务共用同一个 XID 序列。所以回绕是集群级别的威胁不是某个库或某张表独有的。冻结进度是按表跟踪的每张表有自己的 pg_class.relfrozenxid表示这张表里所有 xmin 小于此值的 tuple 都已冻结。不同表的冻结进度完全独立一张频繁更新的表可能 relfrozenxid 很老而另一张只读表可能很新。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2452872.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!