GaussDB(DWS) VACUUM简介

举报
KevinMV 发表于 2021/02/28 17:38:18 2021/02/28
【摘要】 摘要:在GaussDB(DWS)中,VACUUM的本质就是一个“吸尘器”,用于吸收“尘埃”。而尘埃其实就是旧版本数据,如果这些数据没有及时清理,那么将会导致数据库空间膨胀,性能下降,更严重的情况会导致宕机。下面将从VACUUM的作用、用法、原理等方面进行介绍。


1. 前言

  • 适用版本:【8.1.3(及以上)】

在GaussDB(DWS)中,VACUUM的本质就是一个“吸尘器”,用于吸收“尘埃”。而尘埃其实就是旧版本数据,如果这些数据没有及时清理,那么将会导致数据库空间膨胀,性能下降,更严重的情况会导致宕机。下面将对VACUUM的作用、用法、原理以及影响vacuum效果的因素进行介绍。

2. VACUUM的作用

1)空间膨胀问题:清除废旧元组以及相应的索引。包括提交的事务delete的元组(以及索引)、update的旧版本(以及索引),回滚的事务insert的元组(以及索引)、update的新版本(以及索引)、copy导入的元组(以及索引)。

2)freeze:防止因事务ID回卷问题(Transaction ID wraparound)而导致的宕机,将小于OldestXmin的事务号转化为freeze xid,更新表的relfrozenxid,更新库的relfrozenxid,truncate clog。

3)更新统计信息:VACUUM analyze时,会更新统计信息,使得优化器能够选择更好的方案执行sql。

3. VACUUM命令

VACUUM 命令存在两种形式,VACUUM和VACUUM FULL,VACUUM命令做的是LAZY VACUUM。从字面意思就可以看出来,LAZY VACUUM是VACUUM FULL的简化版。具体区别见下表。

LAZY VACUUM VACUUM FULL
空间清理 如果删除的记录位于表的末端,其所占用的空间将会被物理释放并归还操作系统。而如果不是末端数据,会将表中或索引中dead tuple(死亡元组)所占用的空间置为可用状态,从而复用这些空间 不论被清理的数据处于何处,这些数据所占用的空间都将被物理释放并归还于操作系统。当再有数据插入后,分配新的磁盘页面使用
锁类型 共享锁,可以与其他操作并行 排他锁,执行期间基于该表的操作全部挂起
物理空间 不会释放 会释放
事务ID 不回收 回收
执行开销 开销较小,可以定期执行 开销巨大,建议确认数据库所占磁盘页面空间接近临界值再执行操作,且最好选择数据量操作较少的时段完成
执行效果 执行后会有所提升 执行完后,基于该表的操作效率大大提升

注:813版本后GaussDB(DWS)支持列存vacuum回收空间,详见《GaussDB(DWS) 列存VACUUM》

VACUUM在GaussDB(DWS)中具体执行语法如下:

1)回收空间并更新统计信息,对关键字顺序无要求

VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ table_name [ (column_name [, ...] ) ] ]

2)仅回收空间,不更新统计信息

VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name ]

3)回收空间并更新统计信息,且对关键字顺序有要求

VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name [ (column_name [, ...] ) ] ]

重要参数说明:

  • FULL 选择VACUUM FULL清理,可以恢复更多空间,但耗时更多。
  • FREEZE指定FREEZE相当于执行VACUUM时将VACUUM_freeze_min_age参数设为0。
  • VERBOSE为每个表打印一份详细的清理工作
  • ANALYZE | ANALYSE更新用于优化器的统计信息,以决定执行查询的最有效方法。

4. VACUUM原理

4.1 行存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 之前

    1.png


    vacuum 之后

    2.png


4.2 行存LAZY VACUUM执行流程

(1)从指定的多张表中进行遍历,从而获取每一个表。

(2)获取遍历到表的共享锁,该锁允许其他事务读取。

(3)获取每个页面的dead tuples(死亡元组),并freeze需要的元组。

(4)删除指向dead tuples的元组。

(5)删除dead tuples并重新分配live tuples(活动元组)。

(6)更新目标表的FSM(用于记录每个数据块的空闲空)和VM(标记数据块中是否存在需要清理的行)。

(7)重复5,6步骤直到遍历完该表的每一页.

(8)如果最后一页没有元组,则进行截断。

(9)更新与VACUUM有关的统计信息表和系统目录。

4.3 VACUUM FULL执行流程

(1)建立临时表:数据库创建一张临时表,该表继承老表的所有属性。该阶段会申请7级锁(ExclusiveLock),可以与select并发。

(2)数据复制:将原来表中的数据复制到临时表中。在该过程中完成对dead tuples的清理。该阶段申请的是访问排他锁AccessExclusiveLock。

(3)交换表:使用新表代替老表。交换的本质是物理文件的交换,即临时表拿老物理文件,老表拿新物理文件。该阶段会申请八级锁(AccessExclusiveLock)。

(4)重建索引:当交换完成后,会进行索引重建,并更新统计信息。

(5)删除临时表:索引重建完成后,会将带有老物理文件的临时表进行删除。

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

5.1影响vacuum效果的因素

  1. Oldestxmin的推进
    vacuum 只能清理掉当前全局存活的最老事务(OldestXmin)之前的事务所产生的垃圾数据,所以如果仍然存在老事务的话(比如长事务或者长sql的存在),新事务所产生的垃圾数据并不会被vacuum立即清理。例如:
    会话1:新建表row_tbl并插入一条数据,起一个事务先不提交
gaussdb=# create table row_tbl(a int, b int);
CREATE TABLE
gaussdb=# insert into row_tbl values(1,1);
INSERT 0 1
gaussdb=# begin;
BEGIN
gaussdb=# 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 1
gaussdb=# SELECT txid_current_snapshot();
 txid_current_snapshot
-----------------------
 210115:210122:
(1 row)
gaussdb=# vacuum row_tbl;
VACUUM
gaussdb=# insert into row_tbl values(2,2);
INSERT 0 1
gaussdb=# 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;
VACUUM
gaussdb=# 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 1
gaussdb=# select ctid,* from row_tbl;
 ctid  | a | b
-------+---+---
 (0,1) | 3 | 3
 (0,2) | 2 | 2
(2 rows)
  1. LinePointer状态还未处于unused

    3.png


    元组被删除后,只有当vacuum将元组的LinePointer(或者叫item pointer, 指向具体的元组)置为LP_UNUSED状态后,该LinePointer才有可能在新插入数据时复用。
  2. Fsm还未生成
    插入数据时,依赖fsm文件来选择可用的page,如果fsm没有生成则会导致使用新的page而不是复用旧的。
  3. 批量导入
    在旧版本GaussDB(DWS)中,对表进行批量插入数据的操作时,会直接申请新的page来插入数据。所以在某些场景下虽然vacuum后清理了脏数据,但由于业务场景以批量插入为主,导致vacuum对膨胀的控制效果并不理想。目前已经支持批量插入数据时对空间的复用。

5.2 经验之谈

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

6. 总结

vacuum是GaussDB(DWS)中非常重要的一个功能,合理的使用vacuum对于对于那写更新和删除操作频繁的表非常重要,因此需要定期执行清理操作(vacuum)来控制行存表以及表上索引的膨胀,否则就会存在大量垃圾数据,导致磁盘空间的浪费和查询扫描时额外的IO开销。

 


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。