摘要: 对于更新和删除操作频繁的表,会存在大量垃圾数据,导致磁盘空间的节约和查问扫描时额定的IO开销,须要定期执行清理操作(vacuum)来管制行存表以及表上索引的收缩。本文将对vacuum的原理以及影响vacuum成果的因素进行简略介绍。

本文分享自华为云社区《为什么vacuum后表还是持续收缩?》,原文作者:大威天龙:-。

vacuum简介 :

对于GaussDB中的行存表,在更新元组或者删除元组后,旧版本的数据依然存在,仅在元组头信息中标记了删除或更新的事务号(xmax)。对于更新和删除操作频繁的表,会存在大量垃圾数据,导致磁盘空间的节约和查问扫描时额定的IO开销,须要定期执行清理操作(vacuum)来管制行存表以及表上索引的收缩。

vacuum 操作的外部原理:

Vacuum 的次要步骤:

1.移除死亡元组并对满足条件的老元组执行frozen操作。

2.移除指向死亡元组的索引元组,更新对应表的fsm 和 vm 文件

  • FSM: free space map 闲暇空间映射文件,插入数据时会依据该文件来抉择适合的page.
  • VM: visibility map 可见性映射文件,后续vacuum时会依据该文件来抉择是否扫描某个page,进步vacuum效率;同时在进行index-only-scan时也会应用该文件来进步可见性判断的效率)。

3.更新统计数据pg_stat_all_tables。 Linepointer 不会被移除,用于在之后复用。
示意图如下:

vacuum 之前

vacuum 之后

为什么vacuum后表还是持续收缩

影响vacuum成果的因素

1、Oldestxmin的推动
vacuum 只能清理掉以后全局存活的最老事务(OldestXmin)之前的事务所产生的垃圾数据,所以如果依然存在老事务的话(比方长事务或者长sql的存在),新事务所产生的垃圾数据并不会被vacuum立刻清理。例如:

会话1:新建表row_tbl并插入一条数据,起一个事务先不提交

gaussdb=# create table row_tbl(a int, b int);CREATE TABLEgaussdb=# insert into row_tbl values(1,1);INSERT 0 1gaussdb=# begin;BEGINgaussdb=# SELECT txid_current_snapshot(); txid_current_snapshot----------------------- 210115:210115:(1 row)gaussdb=# SELECT txid_current(); txid_current--------------       210115(1 row)

会话2:删除数据后做vacuum清理操作,再次插入数据,数据也没有复用之前空间;查问视图能够发现垃圾数据并没有被清理掉。

gaussdb=# select ctid,* from row_tbl; ctid  | a | b-------+---+--- (0,1) | 1 | 1(1 row)gaussdb=# delete row_tbl;DELETE 1gaussdb=# SELECT txid_current_snapshot(); txid_current_snapshot----------------------- 210115:210122:(1 row)gaussdb=# vacuum row_tbl;VACUUMgaussdb=# insert into row_tbl values(2,2);INSERT 0 1gaussdb=# select ctid,* from row_tbl; ctid  | a | b-------+---+--- (0,2) | 2 | 2(1 row)gaussdb=# select n_dead_tup, last_vacuum from pg_stat_all_tables where relname='row_tbl'; n_dead_tup |          last_vacuum------------+-------------------------------          1 | 2021-06-10 20:04:58.987631+08(1 row)

会话1:将会话1的事务完结再执行vacuum,查问视图能够发现没有死亡元组,插入数据能够发现复用了旧的空间(即ctid是(0,1)的空间)。

gaussdb=# SELECT txid_current_snapshot(); txid_current_snapshot----------------------- 210136:210136:(1 row)gaussdb=# vacuum row_tbl;VACUUMgaussdb=# select n_dead_tup, last_vacuum from pg_stat_all_tables where relname='row_tbl'; n_dead_tup |          last_vacuum------------+-------------------------------          0 | 2021-06-10 20:09:10.516564+08(1 row)gaussdb=# insert into row_tbl values(3,3);INSERT 0 1gaussdb=# select ctid,* from row_tbl; ctid  | a | b-------+---+--- (0,1) | 3 | 3 (0,2) | 2 | 2(2 rows)

2、LinePointer状态还未处于unused

元组被删除后,只有当vacuum将元组的LinePointer(或者叫item pointer, 指向具体的元组)置为LP_UNUSED状态后,该LinePointer才有可能在新插入数据时复用。

3、Fsm还未生成
插入数据时,依赖fsm文件来抉择可用的page,如果fsm没有生成则会导致应用新的page而不是复用旧的。

4、批量导入
在旧版本Gaussdb中,对表进行批量插入数据的操作时,会间接申请新的page来插入数据。所以在某些场景下尽管vacuum后清理了脏数据,但因为业务场景以批量插入为主,导致vacuum对收缩的管制成果并不现实。目前曾经反对批量插入数据时对空间的复用。

一些倡议与总结

  1. 尽量避免长事务,能够通过视图pg_running_xacts查看是否有老事务没有完结或者两阶段事务残留
  2. 定期做vacuum来及时回收垃圾空间
  3. 对于曾经收缩的索引能够通过reindex来放大大小。
  4. vacuum能清理垃圾数据,但无奈将这些空间还给操作系统,对于曾经收缩的表只能通过vacuum full来放大大小。

想理解GuassDB(DWS)更多信息,欢送微信搜寻“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技~

点击关注,第一工夫理解华为云陈腐技术~