共计 2762 个字符,预计需要花费 7 分钟才能阅读完成。
摘要: 对于更新和删除操作频繁的表,会存在大量垃圾数据,导致磁盘空间的节约和查问扫描时额定的 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 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)
2、LinePointer 状态还未处于 unused
元组被删除后,只有当 vacuum 将元组的 LinePointer(或者叫 item pointer, 指向具体的元组)置为 LP_UNUSED 状态后,该 LinePointer 才有可能在新插入数据时复用。
3、Fsm 还未生成
插入数据时,依赖 fsm 文件来抉择可用的 page,如果 fsm 没有生成则会导致应用新的 page 而不是复用旧的。
4、批量导入
在旧版本 Gaussdb 中,对表进行批量插入数据的操作时,会间接申请新的 page 来插入数据。所以在某些场景下尽管 vacuum 后清理了脏数据,但因为业务场景以批量插入为主,导致 vacuum 对收缩的管制成果并不现实。目前曾经反对批量插入数据时对空间的复用。
一些倡议与总结
- 尽量避免长事务,能够通过视图 pg_running_xacts 查看是否有老事务没有完结或者两阶段事务残留
- 定期做 vacuum 来及时回收垃圾空间
- 对于曾经收缩的索引能够通过 reindex 来放大大小。
- vacuum 能清理垃圾数据,但无奈将这些空间还给操作系统,对于曾经收缩的表只能通过 vacuum full 来放大大小。
想理解 GuassDB(DWS)更多信息,欢送微信搜寻“GaussDB DWS”关注微信公众号,和您分享最新最全的 PB 级数仓黑科技~
点击关注,第一工夫理解华为云陈腐技术~