对于更新/删除操作,PostgreSQL的MVCC机制仍会保留先前版本的数据,这些数据在真空时将被清除。但如果在执行真空时,存在先于真空操作的活动事务,假定这些活动事务中最小的事务ID为OldestXmin,那么真空不会清理删除事务ID(即xmax)比;OldestXmin的元组,如果业务繁忙并且OldestXmin事务一直不提交,会导致存储空间一直膨胀,直至耗尽空间。
<强>
实验验证
强>
数据准备
创建一张普通表、插入一行数据
<前>
<代码>
删除表如果存在t_page;
创建表t_page (int id, c1 char (8), c2 varchar (16));
插入t_page值(1,' 1 ',' a ');
代码>
>之前
获取该表对应的数据文件
<前> <代码> testdb 10:26:31 (xdb@(本地):5432)=#选择pg_relation_filepath (“t_page”); pg_relation_filepath ---------------------- 基地/16402/50824 (1行) 代码> >之前查询该数据表占用的空间
<前> <代码> testdb 10:42:43 (xdb@(本地):5432)=# \设置v_tablename t_page testdb 10:43:09 (xdb@(本地):5432)=#选择pg_size_pretty (pg_total_relation_size (: ' v_tablename ')); pg_size_pretty ---------------- 8192个字节 (1行) 代码> >之前
过程
会话1启动事务、会话2执行pg_bench进行测试(在会话1后启动),会话3监控数据表的空间增长/执行真空
会话1
<前>
<代码>
testdb 10:46:48 (xdb@(本地):5432)=#开始;
开始
testdb 10:46:50 (xdb@(本地):5432)=# *选择txid_current ();
txid_current
--------------
397083年
(1行)
testdb 10:46:54 (xdb@(本地):5432)=# *
代码>
>之前
会话2执行pg_bench
<前> <代码> (xdb@localhost脚本)猫test.sql美元 随机\设置id (10000) 开始; 更新t_page组c1=' c1 ' | |:身份证; 提交; 代码> >之前会话3监控数据表的空间增长/执行真空
<前> <代码> ——空间 testdb 10:49:00 (xdb@(本地):5432)=#选择pg_size_pretty (pg_total_relation_size (: ' v_tablename ')); pg_size_pretty ---------------- 192 kB——比;在执行压力测试过程中从8 k增长到192 kb (1行) ——执行真空 testdb 10:49:16 (xdb@(本地):5432)=#真空详细t_page; 信息:吸尘“public.t_page” 信息:“t_page”:发现0可移动,10825年59永久行版本的59页 细节:10824人死亡行版本不能被删除,古老的xmin: 397083 有指针0未使用的项目。 跳过0页由于缓冲针,0冷冻页面。 0页完全是空的。 CPU:用户:0.00,系统:0.00秒,时间:0.00 s。 真空 代码> >之前
真空命令的输出信息:10824人死亡行版本不能被删除,古老的xmin: 397083
因为删除的xmax> OldestXmin,因此这些元组不能被清除。
<强>
源码分析
强>
元组对真空的可见性判断与元组对选择操作的可见性判断类似,选择查询调用的可见性判断函数是HeapTupleSatisfiesMVCC,而真空的可见性判断函数是HeapTupleSatisfiesVacuum,该函数由lazy_scan_heap调用。
lazy_scan_heap
lazy_scan_heap函数的逻辑先前已介绍过,这里不再详述、下面简单梳理与元组清理的相关逻辑
<前>
<代码>
静态的空白
onerel lazy_scan_heap(关系,int选项,LVRelStats * vacrelstats,
关系* Irel, int nindexes bool积极)
{
…
(blkno=0;blkno & lt;nblocks;blkno + +)
{//遍历每个块
…//遍历块中的每个元组
(offnum=FirstOffsetNumber;
offnum & lt;=maxoff;
offnum=OffsetNumberNext (offnum))
{
…
如果(ItemIdIsDead (itemid))
{//记录需删除的元组//vacrelstats→dead_tuples [vacrelstats→num_dead_tuples]=* itemptr;//vacrelstats→num_dead_tuples + +;
lazy_record_dead_tuple (vacrelstats, (tuple.t_self));
all_visible=false;
继续;
}
…//在这里,主要目的是一个元组是否可能对所有正在运行中的事务可见。
开关(HeapTupleSatisfiesVacuum(和元组、OldestXmin buf))
{
案例HEAPTUPLE_DEAD:
…
案例HEAPTUPLE_LIVE:
…
案例HEAPTUPLE_RECENTLY_DEAD://这些元组不能被清除!
nkeep +=1;
all_visible=false;
打破;
…
}
}
}
…
}