博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于PostgreSQL空间膨胀的研究
阅读量:5790 次
发布时间:2019-06-18

本文共 5488 字,大约阅读时间需要 18 分钟。

首先,我们先启用一个数据库自带的控件方便对数据情况进行分析

create extension pgstattuple;

然后,还需要一个存储过程方便快速的制造数据

create function f1(looptime numeric) returns void as $$begin   for i in 1..looptime loop       insert into t1 values(i);   end loop;end;$$ language plpgsql;

我们测试的表为

create table t1(col1 numeric);

以上准备完成后,就开始通过一个实验演示PG的空间膨胀问题,我们对数据库表T1做一些操作:

  1. 写入10000条数据
  2. 删除5000条数据
  3. 再写入5000条数据

那么按我们的理解应该是这样:

10000条数据占据了一定的磁盘空间;删除5000条数据之后,释放了一定的空间;再写入5000条数据,应该填充之前释放的空间。

实际执行情况是:

postgres=# select f1(10000); f1----(1 row)postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 360 kB(1 row)postgres=# delete from t1 where col1<5001;DELETE 5000postgres=# select f1(5000); f1----(1 row)postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 536 kB(1 row)

我们通过两次查询发现t1表的空间从360KB变成了536KB,536/360=1.49,看起来并不像我们想的那样,新写入的5000条数据并没有使用删除5000条数据后空出的空间,而是重新开辟了存储空间,这就是PostgreSQL的空间膨胀问题。

PG的控件膨胀问题要从他的mvcc机制说起。

 

对于任何一个数据库来说多版本控制机制(MVCC)都是保证数据一致性的重要手段。上面图里的3个事务对同一条数据的处理会产生3个不同版本的数据。如果是Oracle数据库,除了最新版本的数据外其他数据都存储在Redo段的前镜像里,对于PG来说,之前版本的数据是存储在表空间内,随着时间和数据操作,表空间内的数据版本会越来越多,而已经不被任何事务需要的数据就会变成死数据(dead_tuple),这些死数据占据了数据库表的空间,又不能被重用,这就造成了表空间膨胀。

PG为了解决这个问题,提供了vacuum机制和autovacuum机制以及HOT机制来清理这些死数据。通过vacuum命令可以手动的发出指令,要求数据库对指定的表清理其死数据,该操作需要锁表。Autovacuum则是数据自动触发的清理操作,来清理死数据,默认1分钟清理1次。

接下来我们把T1表清掉(truncate语句在生产环境请慎用),再执行一次上面的操作,但是在三个位置分别加入1条新的语句

postgres=# truncate table t1;TRUNCATE TABLEpostgres=# select f1(10000);f1----(1 row)postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 360 kB(1 row)postgres=# select * from pgstattuple('t1'); table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------    368640 |       10000 |    290000 |         78.67 |                0 |              0 |                  0 |       7380 |            2(1 row)postgres=# delete from t1 where col1<5001;DELETE 5000postgres=# select * from pgstattuple('t1'); table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------    368640 |        5000 |    145000 |         39.33 |             5000 |         145000 |              39.33 |       7380 |            2(1 row)postgres=# select f1(5000); f1----(1 row)postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 536 kB(1 row)postgres=# select * from pgstattuple('t1'); table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------    548864 |       10000 |    290000 |         52.84 |             5000 |         145000 |              26.42 |       6988 |         1.27(1 row)

这里我们就能很清楚的看到T1表内的死数据了,在执行完一次插入后,表空间内有10000条有效数据,且没有死数据的(tuple_count=10000,dead_tuple_count=0),而我们删除数据以后表中有5000条有效数据,死数据5000,再次插入5000条数据后dead_tuple_count依然是5000,表内的有效数据是10000。就是这些死数据占据了空间。

前面说了PG为了解决这个问题,引入了autovacuum机制,默认是60s触发一次。我们什么都不做,过了一段时间以后,再次用语句检查T1表内的数据情况:

postgres=# select * from pgstattuple('t1'); table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------    548864 |       10000 |    290000 |         52.84 |                0 |              0 |                  0 |     166988 |        30.42(1 row)

发现dead_tuple_count清零了,而且free_percent变成了30.42%,这说明PG的autovacuum起作用了。

我们再次写入5000条数据,并检查空间:

postgres=# select f1(5000);f1----(1 row)postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 536 kB(1 row)postgres=# select * from pgstattuple('t1'); table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------    548864 |       15000 |    435000 |         79.25 |                0 |              0 |                  0 |       6988 |         1.27(1 row)

这次表里的有效数据变成了15000,但表占据的磁盘空间依旧是536KB,这最新的5000条数据利用了autovacuum清理出来的空间,没有新开辟空间了。

不过PG的autovacuum需要一定的资源开销,所以在做PG的性能测试的时候,你会发现图像经常是类似心电图的有规律性的出现性能毛刺(下图蓝色曲线)。

不仅是数据库表会出现这种空间膨胀,索引也会,而且在PG9.3之前autovacuum和vacuum对索引无效,我们在rebuild索引前,只能眼睁睁看着索引越变越大。所幸这个问题在9.6后的版本中解决掉了。

但是vacuum的回收是面对数据库的,不是面对操作系统的。也就是说被数据库占用的磁盘空间不能通过vacuum回收。如果要回收需要通过vacuum full命令。

postgres=# delete from t1 where col1<5001;postgres=# vacuum full t1;VACUUMpostgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 184 kB(1 row)

这里可以看到 在删除表内一半的数据,再通过vacuum full命令处理后,t1表占用的磁盘空间缩小了一半。

由于autovacuum进程的工作间隔,一个表或者索引在期间产生的最大死数据数基本就是膨胀的最大值。

我自己写了一个小脚本,每1秒更新t1表中的2000条数据,t1表一共有10000条数据,执行一段时间后,t1表的大小稳定在如下值:

postgres=# select pg_size_pretty(pg_relation_size('t1')); pg_size_pretty---------------- 4152 kB(1 row)

每秒更新2000条数据,一个autovacuum内(1分钟)会产生120000条死数据,也就是说表膨胀的空间基本会在初始大小的13倍以内,而4152/360=11.5基本验证了我们的说法。

转载于:https://www.cnblogs.com/aegis1019/p/9049478.html

你可能感兴趣的文章
宿舍局域网的应用
查看>>
html代码究竟什么用途
查看>>
Hadoop HDFS编程 API入门系列之路径过滤上传多个文件到HDFS(二)
查看>>
Python version 2.7 required, which was not foun...
查看>>
context:annotation-config vs component-scan
查看>>
经典sql
查看>>
CSS3边框会动的信封
查看>>
JavaWeb实例设计思路(订单管理系统)
查看>>
source insight中的快捷键总结
查看>>
PC-IIS因为端口问题报错的解决方法
查看>>
java四种线程池简介,使用
查看>>
ios View之间的切换 屏幕旋转
查看>>
typedef BOOL(WINAPI *MYFUNC) (HWND,COLORREF,BYTE,DWORD);语句的理解
查看>>
jsp 特殊标签
查看>>
[BZOJ] 1012 [JSOI2008]最大数maxnumber
查看>>
gauss消元
查看>>
多线程-ReentrantLock
查看>>
数据结构之链表与哈希表
查看>>
IIS7/8下提示 HTTP 错误 404.13 - Not Found 请求筛选模块被配置为拒绝超过请求内容长度的请求...
查看>>
http返回状态码含义
查看>>