MogDB发布更新,解决openGauss数据库在长事务情况下Ustore表膨胀问题
近日,云和恩墨更新发布了 MogDB 5.0.11 版本。这一更新带来了一系列重要改进和修复,特别是在Ustore特性增强、存储过程支持SQL语句分析、兼容性提升方面进行了优化,其中最值得关注的是解决了长事务情况下Ustore的表膨胀问题。
在数据库领域,存储引擎的性能和稳定性一直是备受关注的焦点。其中,表膨胀问题可能会严重影响数据库的性能和存储效率,尤其是在长事务场景下。
openGauss 数据库在早期使用的都是Astore存储引擎,其采用追加更新的方式,在同一个page中既存前镜像又存当前值。简单来说,当执行一条update语句时,系统并不会直接修改原数据行,而是标记原数据行过期,然后插入一行更新后的数据(追加写入)。这种更新方式虽然有其优势,但也存在一个明显的问题:由于Astore没有将前镜像数据通过undo分开存放,在对表进行批量update或delete操作后就容易出现表膨胀现象。
为了更直观地理解Astore的表膨胀问题,我们以 openGauss 7.0.0-RC1版本为例进行实验。首先查看数据库版本。
openGauss=# select version();
version
-----------------------------------------------------------------------------------------------------------------------------------------------------------
(openGauss 7.0.0-RC1 build cff7b04d) compiled at 2025-03-28 11:44:23 commit 0 last mr on x86_64-unknown-linux-gnu, compiled by g++ (GCC) 10.3.0, 64-bit
(1 row)
假设现在有一张表test01,它的表结构来自 Oracle 11g DBA_OBJECTS,并且已经反复插入了44,537,344行数据,占用磁盘空间达6115 MB。
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)
接下来,我们创建一张使用Astore存储引擎的表t_astore,并插入100万行数据。
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
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 | test01 | table | opengauss | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | opengauss | 12 MB | {orientation=row,compression=no} |
(3 rows)
可以看到t_astore占用磁盘空间为137 MB。然后,我们对t_astore进行10次大批量的update/delete操作。
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$# end loop;
openGauss$# end;
openGauss$#
ANONYMOUS BLOCK EXECUTE
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 | test01 | table | opengauss | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | opengauss | 12 MB | {orientation=row,compression=no} |
(3 rows)
再次查看表信息,结果显示t_astore占用磁盘空间由之前的137 MB膨胀到了2844 MB,整整增长了近20倍,这就是Astore的表膨胀问题。
后来,openGauss 引入了Ustore存储引擎,采用原位更新方式,通过undo空间存放历史版本数据,在一定程度上缓解了表膨胀问题。我们同样通过实验来验证:创建一张使用Ustore存储引擎的表t_ustore,并插入100万行数据。
openGauss=# create table t_ustore with(storage_type=ustore) as select * from test01 where 1=0;
INSERT 0 0
openGauss=# insert into t_ustore select * from test01 where rownum<=1000000;
INSERT 0 1000000
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_ustore | table | opengauss | 115 MB | {orientation=row,storage_type=ustore,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_ustore占用磁盘空间115 MB。接着,我们对t_ustore进行10次同样的大批量update/delete操作。
openGauss=# declare
openGauss-# v_date date;
openGauss-# begin
openGauss$# for i in 1..10 loop
openGauss$# select sysdate into v_date;
openGauss$# update t_ustore set owner='B' where object_id>2;
openGauss$# commit;
openGauss$# delete from t_ustore where object_id>2;
openGauss$# commit;
openGauss$# insert into t_ustore select * from test01 where object_id>2 and rownum<=999988;
openGauss$# commit;
openGauss$# end loop;
openGauss$# end;
openGauss$#
ANONYMOUS BLOCK EXECUTE
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_ustore | table | opengauss | 232 MB | {orientation=row,storage_type=ustore,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_ustore占用磁盘空间由之前的115 MB增长到232 MB,相比Astore的膨胀20倍,Ustore只增长了1倍,可见Ustore对表膨胀问题有极大的缓解作用。
然而,熟悉 Oracle 的DBA都知道,Oracle 在此种场景中表空间的磁盘占用几乎不会有任何变化。况且前面的测试其实并不严谨,因为没有考虑长事务的情况。那么在长事务场景下,Ustore的表现又如何呢?
我们继续进行实验:创建一张表t_idletransaction,存储引擎采用Astore或Ustore都可以,插入1000行数据;同时创建一张使用Ustore的表t_ustore2,并插入100万行数据。
openGauss=# create table t_idletransaction with(storage_type=astore) as select * from test01 where rownum<=1000;
INSERT 0 1000
openGauss=# create table t_ustore2 with(storage_type=ustore) as select * from test01 where 1=0;
INSERT 0 0
openGauss=# insert into t_ustore2 select * from test01 where rownum<=1000000;
INSERT 0 1000000
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 | 176 kB | {orientation=row,storage_type=astore,compression=no} |
public | t_ustore | table | opengauss | 232 MB | {orientation=row,storage_type=ustore,compression=no} |
public | t_ustore2 | table | opengauss | 115 MB | {orientation=row,storage_type=ustore,compression=no} |
public | test01 | table | opengauss | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | opengauss | 12 MB | {orientation=row,compression=no} |
(6 rows)
此时t_ustore2占用磁盘空间115 MB。接下来,我们在session1中开启事务不提交,模拟长事务。
openGauss=# begin;
BEGIN
openGauss=# update t_idletransaction set object_id=0;
UPDATE 1000
然后在session2中对t_ustore2重复10次大批量update/delete操作。
openGauss=# declare
openGauss-# v_date date;
openGauss-# begin
openGauss$# for i in 1..10 loop
openGauss$# select sysdate into v_date;
openGauss$# update t_ustore2 set owner='B' where object_id>2;
openGauss$# commit;
openGauss$# delete from t_ustore2 where object_id>2;
openGauss$# commit;
openGauss$# insert into t_ustore2 select * from test01 where object_id>2 and rownum<=999988;
openGauss$# commit;
openGauss$# end loop;
openGauss$# end;
openGauss$#
ANONYMOUS BLOCK EXECUTE
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 | t_ustore | table | opengauss | 232 MB | {orientation=row,storage_type=ustore,compression=no} |
public | t_ustore2 | table | opengauss | 1267 MB | {orientation=row,storage_type=ustore,compression=no} |
public | test01 | table | opengauss | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | opengauss | 12 MB | {orientation=row,compression=no} |
(6 rows)
结果令人惊讶,t_ustore2占用磁盘空间由之前的115 MB膨胀到1267 MB,增长了10倍。相比无长事务情况下只膨胀1倍,在有长事务情况下Ustore的表膨胀问题明显恶化,这使得Ustore在这种场景下无法满足商用要求。而且,如果测试 openGauss 更早期的版本,长事务情况下Ustore的表膨胀会更严重。
那么,重点来啦!刚更新不久的 MogDB 5.0.11带来了转机,显著解决了长事务情况下Ustore的表膨胀问题。
首先查看 MogDB 的版本信息。
MogDB=# select version();
version
-------------------------------------------------------------------------------------------------------------------------------------------------------
(MogDB 5.0.11 build 01ca8799) compiled at 2025-03-28 12:46:00 commit 0 last mr 1804 on x86_64-unknown-linux-gnu, compiled by g++ (GCC) 7.3.0, 64-bit
(1 row)
然后进行同样的实验,创建t_idletransaction和t_ustore2表并插入数据。
MogDB=# \d+
List of relations
Schema | Name | Type | Owner | Size | Storage | Description
--------+--------+-------+-------+---------+----------------------------------+-------------
public | test01 | table | omm | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | omm | 12 MB | {orientation=row,compression=no} |
(2 rows)
MogDB=# create table t_idletransaction with(storage_type=astore) as select * from test01 where rownum<=1000;
INSERT 0 1000
MogDB=# create table t_ustore2 with(storage_type=ustore) as select * from test01 where 1=0;
INSERT 0 0
MogDB=# insert into t_ustore2 select * from test01 where rownum<=1000000;
INSERT 0 1000000
MogDB=# \d+
List of relations
Schema | Name | Type | Owner | Size | Storage | Description
--------+-------------------+-------+-------+---------+------------------------------------------------------+-------------
public | t_idletransaction | table | omm | 176 kB | {orientation=row,storage_type=astore,compression=no} |
public | t_ustore2 | table | omm | 115 MB | {orientation=row,storage_type=ustore,compression=no} |
public | test01 | table | omm | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | omm | 12 MB | {orientation=row,compression=no} |
(4 rows)
查看表信息可知t_ustore2占用磁盘空间115 MB。我们接着在session1中开启长事务:
MogDB=# begin;
BEGIN
MogDB=# update t_idletransaction set object_id=0;
UPDATE 1000
在session2中对t_ustore2进行10次大批量update/delete操作。
MogDB=# declare
MogDB-# v_date date;
MogDB-# begin
MogDB$# for i in 1..10 loop
MogDB$# select sysdate into v_date;
MogDB$# update t_ustore2 set owner='B' where object_id>2;
MogDB$# commit;
MogDB$# delete from t_ustore2 where object_id>2;
MogDB$# commit;
MogDB$# insert into t_ustore2 select * from test01 where object_id>2 and rownum<=999988;
MogDB$# commit;
MogDB$# end loop;
MogDB$# end;
MogDB$#
ANONYMOUS BLOCK EXECUTE
MogDB=# \d+
List of relations
Schema | Name | Type | Owner | Size | Storage | Description
--------+-------------------+-------+-------+---------+------------------------------------------------------+-------------
public | t_idletransaction | table | omm | 312 kB | {orientation=row,storage_type=astore,compression=no} |
public | t_ustore2 | table | omm | 371 MB | {orientation=row,storage_type=ustore,compression=no} |
public | test01 | table | omm | 6115 MB | {orientation=row,compression=no} |
public | test02 | table | omm | 12 MB | {orientation=row,compression=no} |
(4 rows)
可以看到,在有长事务的情况下,t_ustore2占用磁盘空间由原来的115 MB膨胀到371 MB,相比 openGauss 中Astore膨胀20倍和Ustore膨胀10倍,MogDB 的Ustore只增长了2倍。
这表明 MogDB 5.0.11 已经有效地缓解了长事务下Ustore的表膨胀问题,使其达到了可商用的条件,为数据库的稳定运行和高效使用提供了有力保障。这一改进对于广大数据库用户和开发者来说无疑是一个好消息,将在实际应用中带来更好的体验。

Ustore特性增强,提升数据处理能力
本次 MogDB 5.0.11版本更新,除了前文中已经通过实验验证过的空间管理优化外,对Ustore存储引擎还进行了多方面的特性增强。
在查询性能上,Ustore开始支持向量化查询,这在大数据量分析查询场景中效果显著,而且能根据执行计划的代价评估结果,动态决定是否使用向量化查询。
在表类型支持上,Ustore现在允许创建全局临时表,这些表对所有会话可见,但每个会话只能操作自己提交的数据。
另外,逻辑复制功能自 MogDB 5.0.8版本开始新增对于DDL操作的支持,减少用户在逻辑复制过程中对表的手动维护,避免由于表结构发生变动导致逻辑复制同步过程出现异常。现在,逻辑复制也支持对Ustore表的DDL操作,确保目标库和源库的表结构一致,避免源库DDL操作导致逻辑复制中断或者数据不一致。
存储过程分析功能升级,精准定位执行语句
MogDB 5.0.11在存储过程方面进行了重大升级,新增了对SQL语句的分析功能。现在,用户可以在 pg_stat_activity 表中直接查看SQL的执行计划,以及存储过程内部正在执行的SQL语句。
在存储过程执行过程中,通过查询 pg_stat_activity 系统表,就能轻松定位当前正在执行的语句。而在存储过程结束后,还能借助 statement_history 查询历史慢SQL,相关的执行计划等信息会自动存入该表中。需要注意的是,慢SQL的判断标准由 log_min_duration_statement 参数控制。
此外,statement_history 表专门用于记录存储过程中的SQL语句及相关信息,不过该表只能在 postgres 库内查询,在其他库中是没有数据的。而且,这个系统表受 track_stmt_stat_level 参数控制,默认值为“OFF,L0”,第一部分控制Full SQL,第二部分控制Slow SQL,用户可以根据需要灵活设置,选择记录不同类型的SQL。
兼容性显著提升,满足多样使用需求
新版本在兼容性方面进行了全面的提升。
在字符处理上,通过设置GUC参数 enable_compatible_illegal_chars,MogDB 5.0.11支持插入非法字符以及C风格字符串结束符'\0'。在trigger语法方面,开启GUC参数 enable_ora_trigger_style 后,不仅支持 Oracle 风格的匿名块方式创建,还支持referencing等语法。同时,该版本还新增了对ROWID伪列的支持,ROWID由“tableoid+ctid”合并计算生成,在事务内能够保证唯一性,即便行数据被更新,也能通过原ROWID正常查询。
在语法支持上,MogDB 5.0.11支持 ALTER TABLE xxx DROP PRIMARY KEY CASCADE DROP INDEX 语法,可自动定位并删除主键约束,极大地提升操作效率。此外,新增的 DBMS_STATS.GATHER_TABLE_STATS 函数接口,可通过更新表的统计信息间接优化查询性能,还可在业务低峰期通过定时任务自动收集统计信息,确保日常查询性能的稳定。
在使用命令方面,当用户通过gsql或工具连接到数据库并完成操作需要退出会话时,除了原有的 \q 和 \quit 命令,现在还可以使用新增的 exit 和 quit 命令。而且,通过设置GUC参数 a_format_date_timestamp = true, current_timestamp 函数对时分秒的支持也得到了增强。最后,系统还会记录同义词及触发器的创建和修改时间,这些信息存储在 pg_object 系统表中,方便用户查询变更历史。
官方发声,强调持续优化决心
云和恩墨数据库基础软件研发负责人张程伟表示:“MogDB 5.0.11的发布,是我们持续致力于为用户提供更优质数据库产品的重要一步。通过不断优化性能、增强功能和提升稳定性,我们希望帮助企业用户更好地应对数字化转型过程中的数据管理挑战。无论是在核心交易系统还是复杂计算场景中,MogDB 都能够为企业提供高效、安全、可靠的数据库环境。”
尽管5.0.11只是 MogDB 5.0.0的补丁版本,但无论是Ustore特性增强、存储过程升级,还是兼容性提升,都展现出云和恩墨在数据库领域持续优化、不断进取的决心。相信这些新特性能够更好地发挥出 MogDB 的优势,为广大用户带来更好的使用体验,提升其业务系统的运行效率,助力企业在数字化加速转型的过程中取得更加高效、稳定地发展。