

点击上方蓝字,关注我们

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 只能清理掉当前全局存活的最老事务(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)
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)
会话3:将会话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)

元组被删除后,只有当vacuum将元组的LinePointer(或者叫item pointer, 指向具体的元组)置为LP_UNUSED状态后,该LinePointer才有可能在新插入数据时复用。
插入数据时,依赖fsm文件来选择可用的page,如果fsm没有生成则会导致使用新的page而不是复用旧的。
在旧版本GaussDB(DWS)中,对表进行批量插入数据的操作时,会直接申请新的page来插入数据。所以在某些场景下虽然vacuum后清理了脏数据,但由于业务场景以批量插入为主,导致vacuum对膨胀的控制效果并不理想。目前已经支持批量插入数据时对空间的复用。
一些建议与总结
1.尽量避免长事务,可以通过视图pg_running_xacts查看是否有老事务没有结束或者两阶段事务残留
2.定期做vacuum来及时回收垃圾空间
3.对于已经膨胀的索引可以通过reindex来缩小大小。
4.vacuum能清理垃圾数据,但无法将这些空间还给操作系统,对于已经膨胀的表只能通过vacuum full来缩小大小。







