暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgresql CommitLog文件与事务ID技术

CommitLog文件

PostgreSQL把事务状态记录在CommitLog(CLog)中。
PostgreSQL 9.X及之前版本中CLOG文件在数据目录的pg_clog子目录下, 从PostgreSQL10版本开始, CLOG文件是在pg_xact子目录下。

事务的状态有以下4种:

  • TRANSACTION_STATUS_IN_PROGRESS=0x00: 表示事务正在进行中。
  • TRANSACTION_STATUS_COMMITTED=0x01: 表示事务已提交。
  • TRANSACTION_STATUS_ABORTED=0x02: 表示事务已回滚。
  • TRANSACTION_STATUS_SUB_COMMITTED=0x03: 表示子事务已提交。

实际上, CommitLog文件是一个位图文件, 因为事务有上述4种状态, 所以需要用两位来表示一个事务的状态。
理论上数据库最多记录20亿个事务, 所以CommitLog最多占用512MB空间。
CommitLog也会被VACUUM清理, 而数据库中的参数“autovacuum_freeze_max_age”的默认设置为2亿, 这样AutoVacuum会尽力保证数据库的事务数是2亿个, 所以通常数据库的CommitLog占用的空间是51MB左右。

可能会有人有这样的疑问, 如果每读到一行时都需要判断这一行上的xmin和xmax代表的事务是否已提交或回滚, 因而都去读CommitLog文件, 这样效率会不会很低?
实际上是不会的, 对此PostgreSQL做了以下优化:

  • PostgreSQL对CommitLog文件进行了Cache, 即在共享内存中有clog buffer, 所以多数情况下不需要读取CommitLog文件。
  • 在每行上有一个标志字段“t_infomask”, 如果标志位“HEAP_XMIN_COMMITTED”被设置, 就知道xmin代表的事务已提交, 则不需要到CommitLog文件中去判断。
    同样, 如果“HEAP_XMAX_COMMITTED”被设置, 就知道xmin代表的事务已提交, 则不需要到CommitLog文件中去判断。

PostgreSQL数据库通过以上优化手段解决了读取行时判断事务状态效率低的问题。

查看当前的TXID号:

SELECT txid_current();

计算记录在哪个CLOG块中:

select (txid_current())/(8192*4) block;

CLog相关源码分析参考:

https://blog.csdn.net/Hehuyi_In/article/details/128115200

CLog详细说明参考:

https://cloud.tencent.com/developer/article/2302333

事务ID技术

事务ID是一个32bit长的数字, 其总是会消耗完的, 消耗完之后会重新从头开始分配, 但这些旧的事务ID已经被分配过, 如果重新分配, 需要先把旧的事务ID回收。
而原先分配的旧事务是写到每个表中每一行的xmin或xmax字段上的, 所以回收旧的事务ID的工作实际上就是清理表中行上的xmin或xmax字段。
此工作是由VACUUM动作来完成的, 如果行上的xmin是较旧的事务ID, 则把其替换成FrozenXID, 即替换成2的值。

如果旧的事务ID未来得及清理会发生什么?
如果我们把40亿个事务ID放到一个环上, 如果最新分配的事务ID与最早的事务ID之间的距离还有1千万时, 数据库会在日志中告警:

WARNING: database "XXX" must be vacuumed within 177009986 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in "XXX".

如果还不处理, 最新分配的事务ID与最早的事务ID之间的距离还有1百万时, 数据库会宕机, 同时在日志文件中打印以下错误日志:

ERROR: database is not accepting commands to avoid wraparound data loss in database "XXXX"

所以我们要特别关注数据库的事务ID回卷问题, 以防因此导致数据库宕机。
一旦出现此情况,会提示使用单用户模式进入然后进行维护:

HINT:  Stop the postmaster and vacuum that database in single-user mode.

单用户模式下维护数据库的时候需要先关闭数据库服务,具体步骤参考:

pg_ctl stop -D /data/pg16
postgres --single -D /data/pg16 dbname
vacuum full dbname;

频繁的插入、更新和删除操作会导致大量的死行即未被回收的行,进而加速事务 ID 的消耗。
应定期监控数据库中的死行,即已删除或已更新但尚未被清理的行和事务 ID 使用情况。
可以使用以下查询:

SELECT age(datfrozenxid) AS frozen_age, 
       datname 
FROM pg_database 
WHERE datname = 'your_database_name'; 

其中datfrozenxid表示在此id之前的所有事务 ID 在数据库中已经被替换为一个永久的(“冻结的”) 事务ID。
这用于跟踪数据库是否需要被清理,以便组织事务ID回环或者允许 pg_xact 被收缩。
它是此数据库中所有表的 pg_class.relfrozenxid 值的最小值。

参考:

https://www.cnblogs.com/zhenren001/p/15654435.html

PG的版本号是uint32的,是重复使用的,所以每隔大约20亿个事务后,必须要冻结,否则记录会变成未来的,对当前事务”不可见”。
同一个数据库中,存在的最旧和最新两个事务之间的年龄最多是2^31,即20亿。
当表的年龄大于autovacuum_freeze_max_age时(默认是2亿),autovacuum进程会自动对表进行freeze。
查询库xid:

SELECT datname, age(datfrozenxid) FROM pg_database;

或者:

select max(age(datfrozenxid)) from pg_database;

查询每个表的xid使用程度:

SELECT c.oid::regclass as table_name, greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age FROM pg_class c LEFT JOIN pg_class t ON c.reltoastrelid = t.oid WHERE c.relkind IN ('r', 'm') order by age desc;

age列度量从该截断 XID 到当前事务 XID 的事务数。

查询按照最老的XID排序,查看大于1G而且是排名前20的表:

SELECT relname, age(relfrozenxid) as xid_age, pg_size_pretty(pg_table_size(oid)) as table_size FROM pg_class WHERE relkind = 'r' and pg_table_size(oid) > 1073741824 ORDER BY age(relfrozenxid) DESC LIMIT 20;

建议使用vacuum freeze [ table_name ] 来对指定的表进行xid 冻结清理。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论