写点什么

为什么 vacuum 后表还是继续膨胀?

发布于: 1 小时前

摘要: 对于更新和删除操作频繁的表,会存在大量垃圾数据,导致磁盘空间的浪费和查询扫描时额外的 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 级数仓黑科技~


点击关注,第一时间了解华为云新鲜技术~

发布于: 1 小时前阅读数: 3
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
为什么vacuum后表还是继续膨胀?