概述¶
在数据库系统的日常运行中,表膨胀和索引碎片是影响系统性能的常见问题。随着数据库使用时间的延长和数据量的增长,PostgreSQL数据库中的表和索引文件可能会逐渐膨胀,超出实际存储数据所需的大小。这种现象不仅会浪费宝贵的磁盘空间,还会显著降低查询性能,增加系统资源消耗。
表膨胀是指PostgreSQL数据库中的数据文件大小显著超出了实际存储有效数据所需的大小。在PostgreSQL中,由于MVCC(多版本并发控制)机制,当执行DELETE或UPDATE操作时,数据库并不会立即物理删除旧的数据行,而是将其标记为"死亡元组"(dead tuple)。这些死亡元组仍然占用磁盘空间,随着时间推移和操作积累,表文件会变得越来越大,形成表膨胀。
填充因子¶
fillfactor是在创建表的时候指定的参数,该参数是限制数据插入一页时预留的空闲空间比例,对于数据库表的默认值是100,索引默认值是90
一个表的填充因子是一个10-100质检的百分数。100(完全填满)是默认值。设置较小的填充因子,insert操作会把表页面只填满到指定的百分比,剩余的空间留给页面上行的更新。这就让update有机会把一行的已更新版本放到在与原始版本相同的页面上,这比把它放在一个不同的页面上效率更高。
-
对于不经常更新的表来说,设置为100是最好的选择,如果更新频繁设置较小的值更合适
-
这个参数对toast表不生效。
表膨胀问题¶
PostgreSQL表膨胀的原因主要有两个:一个是垃圾数据,即dead tuple行数太多未及时清理,导致不能及时提供能重用的空间,二是数据页之间存在空闲空间
Postgresql在数据修改时通过保留数据的历史版本来实现MVCC,也即不同的事务要看到同一条数据的不同版本,这需要依次保留不同版本的问题。
不同的数据库的MVCC机制实现是不同的,MySQL或者Oracle中是通过将历史记录写入undo表空间实现,PostgreSQL是直接在当前页面保留这个数据的历史版本。
也就是说:数据行的变更,都是直接存在表的数据页上了,历史数据都存着呢
粗略来看一条update或者delete发生时是如何实现多版本的。
数据修改操作:将某一行的data字段从a修改为b
可以直观地想象一下Postgresql中修改一条记录事生成的“undo”记录的实现,(当然除此之外这个undo记录与xlog有关)
其过程就是update的时候保留老的记录,重新写入一条新纪录的, 通过不同的事务Id决定不同的事务可以看到修改前或者修改后的记录
数据删除操作:这里示例删除上面修改后的记录的过程
删除操作是类似的一个过程,仅标记原始记录被删除(set t_xmax),但此时记录还保存在原地。
这里就存在2个问题:
1、谁&什么时候&什么条件下,清理历史版本
大量的历史版本会造成表膨胀的问题,不过目前看来应该不是问题,绝大多数情况下后台清理进程完全可以hold的住。
其实这个问题源自于MVCC需要保留不同版本数据的机制造成的,是一个支持MVCC的共性问题,MySQL中也有类似问题,MySQL 5.7之前undo 表空间膨胀且无法装直接收缩,业内也为此整出来各种奇淫巧技来处理该问题、所以某些问题是必须要经历或者说面对的,没有绝对好或者绝对坏的方法。