PG表膨胀原因
PG特殊的MVCC机制,delete不会真的删除元组,update相当于delete+insert,旧元组本身不能通过DML语句来删除,这样就只有“涨”空间没有“清理”空间,这就是表膨胀。
此时一般需要vacuum来清理死元组,把空间标记为可用,下次写入时可以用到这部分空间;或者vacuum full等方式重写表,让表变得更加紧凑。
场景复现
OS:CentOS 7.6
db版本:PG 14.2
表名: cmdb_objects
图片
表结构:
图片
我先插入两条数据,然后进行循环插入,只有object_type字段数据不同,用于后续做更新操作。
insert into cmdb_objects values ('xgrdb','xgr','test67','index');
insert into cmdb_objects values ('xgrdb','xgr','test67','table');
insert into cmdb_objects select * from cmdb_objects;
现在该表中存在32768条数据,再进行update更新操作:
update cmdb_objects set object_type='view' where object_type='table';
图片
更新后该表会存在16384死行,如业务数据量大,且有频繁的更新操作,该表就会产生高水位。可通过pg_stat_all_tables视图来查询该表中存在的死行、活跃行数。
select relname,n_live_tup,n_dead_tup from pg_stat_all_tables where relname='cmdb_objects';
图片
处理方式
第一种处理方法vacuum:
把表中的dead tuples进行标记删除,并没有真正的物理删除,表的实际使用空间没有减小,只是将dead tuples的航空件转换为可以使用的状态,vacuum过程中,可以正常访问表数据,不锁表。
你也可以在执行碎片清理时收集统计信息:
vacuum(verbose,analyze) test;
16384死行已被删除(该方式适用于实时更新的,更新数据量不大的表,可以每天进行批量操作)。
第二种处理方法vacuum full:
物理删除表中的dead tuples,释放空间给操作系统;vacuum full过程中,表被锁定,不允许访问。
图片
执行前表大小如下:
图片
执行vacuum full:
图片
执行后表大小:
可以看到该表的使用空间已被物理删除释放。(该方式通过重建表,回收所有碎片空间,适用于经常进行大批量更新数据的表,定制策略进行执行,也可以在维护时间业务表每周执行一次。)
总结一下vacuum 与 vacuum full的区别:
- Vacuum不会阻塞查询和DML业务;不会立即回收空间,只是把空间标记为可用,降低高水位;如果表的最后一个page没有元组了,这个page会被truncate。
- Vacuum ful8级锁,阻塞一切;表完全重写,操作系统上对应的文件会被清理和重建;重建索引、FSM(可用空间文件)、VM (page可见性文件);会创建一个副本表,该表使用的磁盘空间最大可能翻至2倍,磁盘空间不足,谨慎执行。
第三种处理方法autovacuum参数控制:
图片
autovacuum是postgres里面一个服务端进程,可在一定条件下自动触发执行。
该参数值默认为 on(开启全局自动),主要作用包括:
- 清理死元组(UPDATE或DELETE操作后留下的),并对表进行分析;
- 更新可用空间映射(free space map),以跟踪表块中的可用空间;
- 更新仅索引扫描所需的可见性图(visibility map);
- 冻结(freeze)表行,以便事务ID计数器可以安全地环绕。
表膨胀的危害:
- 表占用过大的空间;
- 进而引起sql性能降低;
- 表过大会也会导致vacuum清理时间变成长;vacuum full阻塞时间也会变长,不过可以通过pg_repack来代替vacuum full,减少阻塞时间。
以上操作都会增加cpu与io的资源消耗。
补充:有一种表膨胀情况比较难处理,autovacuum自动清理死元组速度赶不上生成速度。因为update、 insert、delete事务并发太多,这次的vacuum还没来得及清理出之前的可用空间,就有大量update生成新的空间和死元组,导致表不断膨胀。