PostGreSQL/openGauss表膨胀处理

如果面试官问你,Oracle与PG/OG最大的区别是什么?你要是没回答出MVCC机制,表膨胀,那你多半挂了。

在PG/OG数据库中,命令vacuum full,插件pg_repack用于处理表膨胀,但是别高兴得太早,如果有长事务,vacuum full执行完之后空间还是无法回收。

pg_repack需要等执行pg_repack命令时之前的长事务都提交了才能往后执行,你愿意等吗?很明显不愿意。

下面我们来模拟一下有长事务情况下vacuum full无法回收空间的问题

现在有张表test01,表结构来自Oracle11g DBA_OBJECTS,反复插入了44537344行数据,占用磁盘空间6115 MB

sql 复制代码
openGauss=# \d+
                                       List of relations
 Schema |  Name  | Type  |   Owner   |  Size   |             Storage              | Description 
--------+--------+-------+-----------+---------+----------------------------------+-------------
 public | test01 | table | opengauss | 6115 MB | {orientation=row,compression=no} | 
 public | test02 | table | opengauss | 12 MB   | {orientation=row,compression=no} | 
(2 rows)

创建2个测试表t_idletransaction,t_astore

sql 复制代码
openGauss=# create table t_idletransaction with(storage_type=astore) as select * from test01 where rownum<=1000;
INSERT 0 1000
openGauss=# create table t_astore with(storage_type=astore) as select * from test01 where 1=0;
INSERT 0 0
openGauss=# insert into t_astore select * from test01 where rownum<=1000000;
INSERT 0 1000000

记录表大小

sql 复制代码
openGauss=# \d+
                                                       List of relations
 Schema |       Name        | Type  |   Owner   |  Size   |                       Storage                        | Description 
--------+-------------------+-------+-----------+---------+------------------------------------------------------+-------------
 public | t_astore          | table | opengauss | 137 MB  | {orientation=row,storage_type=astore,compression=no} | 
 public | t_idletransaction | table | opengauss | 176 kB  | {orientation=row,storage_type=astore,compression=no} | 
 public | test01            | table | opengauss | 6115 MB | {orientation=row,compression=no}                     | 
 public | test02            | table | opengauss | 12 MB   | {orientation=row,compression=no}                     | 
(4 rows)

session1 开启长事务

sql 复制代码
openGauss=# begin;
BEGIN
openGauss=# update t_idletransaction set object_id=0;
UPDATE 1000

session2 反复对表进行批量DML操作

sql 复制代码
penGauss=# \timing
Timing is on.
openGauss=# declare
openGauss-# v_date date;
openGauss-# begin
openGauss$#   for i in 1..10 loop
openGauss$#     select sysdate into v_date;
openGauss$#     update t_astore set owner='B' where object_id>2; 
openGauss$#     commit;
openGauss$#     delete from t_astore where object_id>2;
openGauss$#     commit;
openGauss$#     insert into t_astore select * from test01 where object_id>2 and rownum<=999988;
openGauss$#     commit;
openGauss$#     raise info 'i=%,current_date=%',i,v_date;
openGauss$#   end loop;
openGauss$# end;
openGauss$# /
INFO:  i=1,current_date=2025-04-07 15:33:43
INFO:  i=2,current_date=2025-04-07 15:33:48
INFO:  i=3,current_date=2025-04-07 15:33:56
INFO:  i=4,current_date=2025-04-07 15:34:04
INFO:  i=5,current_date=2025-04-07 15:34:14
INFO:  i=6,current_date=2025-04-07 15:34:26
INFO:  i=7,current_date=2025-04-07 15:34:39
INFO:  i=8,current_date=2025-04-07 15:34:52
INFO:  i=9,current_date=2025-04-07 15:35:07
INFO:  i=10,current_date=2025-04-07 15:35:23
ANONYMOUS BLOCK EXECUTE
Time: 117272.747 ms

查看表大小

sql 复制代码
openGauss=# \d+
                                                       List of relations
 Schema |       Name        | Type  |   Owner   |  Size   |                       Storage                        | Description 
--------+-------------------+-------+-----------+---------+------------------------------------------------------+-------------
 public | t_astore          | table | opengauss | 2844 MB | {orientation=row,storage_type=astore,compression=no} | 
 public | t_idletransaction | table | opengauss | 312 kB  | {orientation=row,storage_type=astore,compression=no} | 
 public | test01            | table | opengauss | 6115 MB | {orientation=row,compression=no}                     | 
 public | test02            | table | opengauss | 12 MB   | {orientation=row,compression=no}                     | 
(4 rows)

t_astore从137MB膨胀到2844MB,膨胀了20倍,执行vacuum full回收空间

sql 复制代码
openGauss=# vacuum full t_astore;
VACUUM
Time: 41075.170 ms
openGauss=# \d+
                                                       List of relations
 Schema |       Name        | Type  |   Owner   |  Size   |                       Storage                        | Description 
--------+-------------------+-------+-----------+---------+------------------------------------------------------+-------------
 public | t_astore          | table | opengauss | 2847 MB | {orientation=row,storage_type=astore,compression=no} | 
 public | t_idletransaction | table | opengauss | 312 kB  | {orientation=row,storage_type=astore,compression=no} | 
 public | test01            | table | opengauss | 6115 MB | {orientation=row,compression=no}                     | 
 public | test02            | table | opengauss | 12 MB   | {orientation=row,compression=no}                     | 
(4 rows)

t_astore从2844MB变到了2847MB,这说明在有长事务情况下,vacuum full不能回收空间

不得不说PG/OG的vacuum full做得很挫,我这里长事务的表是t_idletransaction,vacuum full的表是t_astore,两个表都没啥关系,还不让回收空间

内核只考虑怎么实现方便,不考虑怎么使用方便,嘿嘿

既然vacuum full无法回收,那就手工回收空间吧

1.检查表上有没有锁,如果有,不执行后面操作

2.开启事务

3.先锁住原表

4.创建一个备份表

5.truncate原表

6.把备份数据插入到原表

7.drop备份表

8.提交

sql 复制代码
openGauss=# begin;
BEGIN
Time: 0.222 ms
openGauss=# lock table t_astore;
LOCK TABLE
Time: 1002.649 ms
openGauss=# create table t_astore_bak as select * from t_astore;
INSERT 0 999999
Time: 5381.672 ms
openGauss=# truncate table t_astore;
TRUNCATE TABLE
Time: 2.081 ms
openGauss=# insert into t_astore select * from t_astore_bak;
INSERT 0 999999
Time: 628.223 ms
openGauss=# drop table t_astore_bak;
DROP TABLE
Time: 1.363 ms
openGauss=# commit;
COMMIT
Time: 235.081 ms

openGauss=# \d+
                                                       List of relations
 Schema |       Name        | Type  |   Owner   |  Size   |                       Storage                        | Description 
--------+-------------------+-------+-----------+---------+------------------------------------------------------+-------------
 public | t_astore          | table | opengauss | 137 MB  | {orientation=row,storage_type=astore,compression=no} | 
 public | t_idletransaction | table | opengauss | 312 kB  | {orientation=row,storage_type=astore,compression=no} | 
 public | test01            | table | opengauss | 6115 MB | {orientation=row,compression=no}                     | 
 public | test02            | table | opengauss | 12 MB   | {orientation=row,compression=no}                     | 
(4 rows)

可以看到t_astore空间已经回收,表从2847MB缩小到137MB

为什么要先锁住原表呢?是因为如果能锁住原表,说明表当前没有被select,也没有在事务中,对表人工进行空间回收是安全的

在openGauss中,select没执行完,另外的会话无法lock表

session1

select count(*) from test01 a,test01 b; ---4.4kw与4.4kw笛卡尔积,跑不出结果

session2

begin;

lock table test01; ---阻塞

在openGauss中,表在事务中,另外的会话无法lock表

session1

begin;

select * from test01 where object_id=1000;

session2

begin;

lock table test01; ---阻塞

通过上面2个实验,证明了手工回收空间操作是安全的

有的读者可能会问,你为什么要折腾出人工回收空间的方法呢?

那是因为之前在一个制造业客户中遇到了表膨胀问题,客户每个月月初要做月结,每次月结都会卡死2小时,客户一直在抱怨

后面定位到是索引膨胀,索引上死元组有3到4000W行,索引是十几个字段组成的联合主键,由于死元组有几千万行,批量INSERT到

该表,每插入一行数据就要进行唯一性检查,死元组太多了,外加上联合主键的原因导致唯一性检查特别慢,就这一步卡死2小时,正常情况下几十秒跑完

由于vacuum full回收不了空间,pg_repack也回收不了,所以被逼折腾出人工回收空间的方法

最后搞个了crontab,每隔一段时间检查n_dead_tup,当n_dead_tup大于某个阈值,检查表上有没有锁,没有锁就人工回收空间,自此之后,每次月结跑批就很稳定了

我做实验的表比较小,如果表特别大,几十GB,上百GB就不好处理了,所以大表最好分区,分区之后可以按照分区单独处理表膨胀

限于篇幅,就不讨论ustore了,下一篇讨论openGauss/GaussDB ustore

注意:人工处理表膨胀相当于把表上面的历史版本数据丢了,如果你有基于快照做备份恢复的需求,自己做个实验测试一把上面操作对你是否有影响

相关推荐
神秘剑客_CN3 天前
openEuler 22.03安装openGauss 6.0
linux·opengauss·openeuler
Gauss松鼠会4 天前
Prometheus 实现 openGauss 的指标监控(二)Prometheus 中添加 openGauss指标
网络·数据库·oracle·prometheus·opengauss·经验总结
赵渝强老师1 个月前
【赵渝强老师】高斯数据库(openGauss)的模式
数据库·opengauss·国产数据库·高斯数据库
赵渝强老师2 个月前
【赵渝强老师】openGauss的数据库
数据库·opengauss·国产数据库·高斯数据库
赵渝强老师4 个月前
【赵渝强老师】高斯数据库(openGauss)的逻辑存储结构
数据库·postgresql·opengauss·gaussdb·国产数据库·高斯数据库
赵渝强老师4 个月前
【赵渝强老师】高斯数据库(openGauss)的体系架构
数据库·postgresql·opengauss·gaussdb·国产数据库
Gauss松鼠会4 个月前
【GaussDB】GaussDB 表的创建与使用之临时表
数据库·database·opengauss·gaussdb
Gauss松鼠会5 个月前
openGauss数据库源码解析系列文章——存储引擎源码解析(一)
数据库·oracle·性能优化·database·opengauss
Gauss松鼠会5 个月前
openGauss数据库源码解析系列文章--openGauss简介(上)
数据库·性能优化·database·opengauss
一点多余.6 个月前
openGauss 企业版安装全流程指南
linux·数据库·opengauss·企业版