一、 Freezing 冻结
1. 引入原因
简单说来就是目前pg事务id只有32位,大业务量下很可能用完,触发事务id回卷(循环使用)。而pg是根据事务id大小判断可见性的,如果新事务却使用了小id,旧事务将可以看到新事务数据,新事务又看不到旧事务数据,打破数据一致性。
详情参考:pg事务篇(二)—— 事务ID回卷与事务冻结(freeze)_Hehuyi_In的博客-CSDN博客_vacuum_freeze_min_age
2. 冻结原理
pg将超出database horizond的事务id做一个特殊标记,即进行冻结(源码中是将其事务id设为2)。被冻结的事务被认为比所有事务都要旧,这样即便事务id回卷,只要旧事务id已被冻结,小的事务id也可以看到它(正常事务id从3开始),符合数据一致性。
3. 测试案例
先更新下老朋友heap_page函数,查看更多信息
CREATE FUNCTION heap_page(
relname text, pageno_from integer, pageno_to integer
)
RETURNS TABLE(
ctid tid, state text,
xmin text, xmin_age integer, xmax text
) AS $$
SELECT (pageno,lp)::text::tid AS ctid,
CASE lp_flags
WHEN 0 THEN 'unused'
WHEN 1 THEN 'normal'
WHEN 2 THEN 'redirect to '||lp_off
WHEN 3 THEN 'dead'
END AS state,
t_xmin || CASE
WHEN (t_infomask & 256+512) = 256+512 THEN ' f'
WHEN (t_infomask & 256) > 0 THEN ' c'
WHEN (t_infomask & 512) > 0 THEN ' a'
ELSE ''
END AS xmin,
age(t_xmin) AS xmin_age,
t_xmax || CASE
WHEN (t_infomask & 1024) > 0 THEN ' c'
WHEN (t_infomask & 2048) > 0 THEN ' a'
ELSE ''
END AS xmax
FROM generate_series(pageno_from, pageno_to) p(pageno),
heap_page_items(get_raw_page(relname, pageno))
ORDER BY pageno, lp;
$$ LANGUAGE sql;
创建测试表,fillfactor = 10是为了使每页可容纳元组数变小(例如案例中仅为每页只能存2个元组)。
CREATE TABLE tfreeze(id integer,s char(300)) WITH (fillfactor = 10, autovacuum_enabled = off);
INSERT INTO tfreeze(id, s) SELECT id, 'FOO'||id FROM generate_series(1,100) id;
VACUUM tfreeze;
SELECT * FROM generate_series(0,1) g(blkno),pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
SELECT * FROM heap_page('tfreeze',0,1);
xmin_age为1因为我们是当前db中最新的事务
4. 冻结相关参数
- vacuum_freeze_min_age:自上次冻结后,经过多少元组年龄(t_xmin-pg_class.relfrozenxid)会触发下次冻结,考虑vm文件。默认值为5000万。
- vacuum_freeze_table_age:自上次冻结后,经过多少表年龄(表中最新xid- pg_class.relfrozenxid)达到该值会触发表 aggressive freezing,不考虑vm文件。默认值为1.5亿。
- autovacuum_freeze_max_age:自上次冻结后,经过多少元组年龄(t_xmin-pg_class.relfrozenxid),元组对应表会强制触发autovacuum(即便设置为off)。默认值为2亿。
- vacuum_failsafe_age:pg 14新参数,当出现事务id回卷风险(元组年龄大于vacuum_failsafe_age)时,以最快速度完成冻结操作。默认值为16亿,pg假设此变量值大于autovacuum_freeze_max_age。
1)vacuum_freeze_min_age
为了方便观察,我们把vacuum_freeze_min_age改为1(测试完记得改回来)
ALTER SYSTEM SET vacuum_freeze_min_age = 1;
-- 反向操作
-- alter system reset vacuum_freeze_min_age;
SELECT pg_reload_conf();
UPDATE tfreeze SET s = 'BAR' WHERE id = 1;
SELECT * FROM heap_page('tfreeze',0,1);
可以看到xmin_age已经大于1,但并不会自动触发冻结
SELECT * FROM generate_series(0,1) g(blkno),pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
update后0号页在vm映射中并非完全可见,会被vacuum处理。
执行vacuum触发冻结
VACUUM tfreeze;
SELECT * FROM heap_page('tfreeze',0,1);
(0,1)是第一个元组,被保留并重定向;(0,2) xmin_age=2,被冻结;(0,3) xmin_age=1,不需要处理。1号页的元组因为vm中完全可见,会被vacuum跳过。
SELECT * FROM generate_series(0,1) g(blkno),pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
冻结后0号页的元组也变为vm中完全可见
2)vacuum_freeze_table_age
如上面实验,vacuum_freeze_min_age会考虑vm文件,如果这些页一直可见,其中元组事务id就一直不会被冻结。
因此,我们引入一个新参数vacuum_freeze_table_age,它无视vm文件,只要表年龄达到一定值,表中的页就会被冻结(专有名词,aggressive freezing)。
3)autovacuum_freeze_max_age
前面两个参数针对的都是vacuum操作,如果关闭了表的autovacuum设置而又不手动执行vacuum,长此以往会遇到问题,另外一些系统库例如template0也不能手动vacuum。因此pg引入了本参数,当元组年龄达到时强制触发autovacuum(即便设置为off)。
默认值为2亿,最大值为20亿,这考虑了事务id最大为42亿,保证在两次freeze之间,txid的增长肯定不会超过20亿。
autovacuum_freeze_max_age参数值也会影响CLOG,因为没有必要记录已冻结事务的状态,小于datfrozenxid的事务对应的CLOG将会被autovacuum删除。
对于上面提到的3个参数,表级也有对应autovacuum参数
- • autovacuum_freeze_min_age and toast.autovacuum_freeze_min_age
- • autovacuum_freeze_table_age and toast.autovacuum_freeze_table_age
- • autovacuum_freeze_max_age and toast.autovacuum_freeze_max_age
4)vacuum_failsafe_age
pg 14新参数,当出现事务id回卷风险(元组年龄大于vacuum_failsafe_age)时,触发全速vacuum模式,忽略autovacuum_vacuum_cost_delay 和vacuum_cost_delay参数,跳过索引vacuum,以最快速度完成冻结操作。
5. 手动冻结
- vacuum freeze命令:冻结所有可见元组而不管它们的age,相当于vacuum_freeze_min_age = 0
- 导入数据时冻结:COPY tfreeze FROM stdin WITH FREEZE;
未完待续...