结论:PG数据库导致表膨胀的原因是逻辑删除的历史数据清除不及时
表及索引的膨胀
主流的MVCC机制如下:
1、以Oracle和Innodb为例,写如新数据时,把旧数据转移到一个单独的地方,如回滚段中,如果其他人读取数据时,从回滚段中把数据读取出来,可以有效避免膨胀。
2、以SQL Server为代表的,把旧版本的的数据写入专门的临时表空间中,新数据写入日志中,然后去更改数据,也可以有效地避免膨胀。
3、PG修改时,旧数据不删除,只是标注为无效(逻辑删除,在某个时间再去清理)
Oracle和Innodb的undo实现形式:
Oracle最经典的错误就是回滚段快照过旧
,意思是回滚段中的数据还在被某些事务使用,但是数据在回滚段中已经被删除了
,Innodb也有这样的问题。
如果在一个超大事务执行过程中,事务失败了
,它会回滚,产生大量cpu和IO占用(因为要从undo segment回滚段,导入data segment中)。
PG的优点
1、无论进行多少操作,事务回滚都会立即完成(秒级回滚)
,Oracle使用了回滚段,如果Oracle宕机时很多事务正在运行,再次启动后,需要把之前的事务回滚(具体的一会儿再说),当没有回滚完成时数据行上仍然有锁,业务不能正常操作,如果需要回滚的业务量很大,情况会很坏。
2、可以放心执行超大事务
不必像Oracle和Innodb
那样需要经常保证回滚段不会被用完
,也不用像Oracle那样会遇到ORA-1555错误的困扰。
PG的缺点:
1、旧版本数据需要清理
,在8.3版本引入了autovacuum,采用多进程架构,支持多表的同时操作。
2、旧版本的数据清理不及时会导致性能下降
。
3、空间持续上涨,存储没有得到有效的利用
。
原因:
表及索引膨胀是指随着时间推移
,表和索引的物理存储结构
逐渐变得不连续或者产生空洞
,导致数据库性能下降
的问题。这种情况通常由以下几个原因引起:
1、更新和删除操作频繁(vacuum会清理它留下来的历史版本):
当表中的行经常被更新或删除时,PostgreSQL 的MVCC(多版本并发控制)机制
(历史数据回收不及时)可能会导致数据页面的分裂和空洞的产生
。例如,当更新或删除一行后,原来的数据页可能留下了空洞或者变得不再紧凑
。
2、自动化的 VACUUM 过程不足够频繁:
PostgreSQL 使用 VACUUM 进程来回收被删除行所占用的空间
,并且重新组织数据以减少表和索引的膨胀
。如果 VACUUM 过程不足够频繁
,或者数据库负载很高
,可能会导致表和索引的膨胀问题积累。
3、长时间运行的事务:
长时间运行的事务可以阻碍 VACUUM 进程的正常工作
,从而使得表和索引无法及时进行空间的回收和重新组织。
4、未正确设置和优化参数:
PostgreSQL 的参数设置
(如 autovacuum 相关参数、vacuum_cost_delay、maintenance_work_mem 等)可能影响 VACUUM 进程的执行效率和频率,进而影响表和索引的膨胀问题的处理。
解决方法:
定期监控数据库的性能指标
,如表和索引的空间利用率,以及 VACUUM 进程的运行情况
。根据监控结果调整数据库配置和优化策略。
常见问题及处理流程
表膨胀查询SQL(安装表膨胀系数倒叙排序)
SELECT
current_database() AS db, schemaname, tablename,
ROUND(CASE WHEN otta=0 OR sml.relpages=0 OR sml.relpages=otta THEN 0.0 ELSE sml.relpages/otta::numeric END,1) AS tbloat,
CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
CASE WHEN relpages < otta THEN
CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
END AS totalwastedbytes
FROM (
SELECT
nn.nspname AS schemaname,
cc.relname AS tablename,
COALESCE(cc.reltuples,0) AS reltuples,
COALESCE(cc.relpages,0) AS relpages,
COALESCE(bs,0) AS bs,
COALESCE(CEIL((cc.reltuples*((datahdr+ma-
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float