PostgreSQL DBA (27) - MVCC # 7(避免长事务)

  

  对于更新/删除操作,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;   打破;   …   }   }   }   …   }   

PostgreSQL DBA (27) - MVCC # 7(避免长事务)