干货|分析PostgreSql单表60w数据却占用55g空间

2023-10-25 15:47:17 浏览数 (3)

突然听到运维说磁盘预发布环境磁盘空间不够,细查之下发现是由于某个表的数据太大导致的,但是查看了下数据库表发现,实际的表数据量只有60w条,很明显表哪里出问题了,一开始以为是犹豫表的设计不合理索引导致的数据量大,细看之下发现挺正常的。正在焦虑蹉跎的时候,有幸得到朋友圈大佬的指点,是死亡元组太多导致的只需要执行vacuum full清理死亡元组就好,查看了相关的博客稳定发现postgresql居然会保存mvcc多版本修改记录,简单理解就是,postgresql对你所做的修改和删除都会保存记录,不会清理释放空间。这让我顿时想到[Mysql的MVCC],但是mysql的undo log也只记录执行操作的相反记录保留最新的记录,而redo log记录数据页的变更,但是大小是固定的,都可以通过配置参数配置固定大小。

# 单表超过55g实际数据却只有60w条 

回到postgresql数据库上,一开始提到的死亡元组问题提到了[VACUUM命令](http://postgres.cn/docs/10/sql-vacuum.html) 简单了解之下只是看到一些博客说pg会保留更新删除数据行的MVCC版本记录数据,完了又看到官网的解释:   `简单的 VACUUM(不带FULL)简单地收回空间并使其可以被重用。` 敲黑板画重**被重用**,标记为垃圾空间方便重用,就好像jvm标记了垃圾,但是不清理,只是后面会被覆盖,这时我就觉得很扯,为什么不清理,这么一直留着只会表的数据越来越大只增不减,而且还可能误读,感觉好鸡肋啊,紧接着继续研究为什么会产生这么多空间,虽然细细的研究之下我发现官网一句特别的提示:   `24.1.2. 恢复磁盘空间 在PostgreSQL中,一次行的UPDATE或DELETE不会立即移除该行的旧版本。这种方法对于从多版本并发控制(MVCC,见第 13 章)获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行VACUUM来完成。`    或许标记重用只是为了保留之后可以回溯历史,方便事务记录查询,详细见[24.1.2. 恢复磁盘空间](http://www.postgres.cn/docs/10/routine-vacuuming.html)这就解释了为什么一个表明明只有60w数据却空间占用55g,一条记录被更新之后他的快照依然会保留,不会立刻删除,当更新或者删除特别频繁的时候,空间占用就会特别的明显了,vacuum命令类似于标记一些过时的数据为垃圾数据(这有点像操作系统,当你的把数据删除了,其实他只是标记删除,完了继续堆积在新的未存放数据的空间,这就是说为什么理论上,不存在彻底的删除,除非你把磁盘填满之后重新覆盖),可以被之后的新记录覆盖,对于急着释放空间页面请求又不是特别多的情况下还是需要vacuum full来紧急释放空间,另外官网也不建议频繁的vacuum full来代替vacuum毕竟,况且使用vacuum full会锁住整个表,之前的预发布环境中,也整整执行了6分钟,这是非常不理想的,万一用户在使用呢这就很不友好了,况且版本记录有时候还是有用的。

注意

其实官网也不建议频繁的使用vacuum full来清理死亡元祖,除非需要立刻释放空间,毕竟数据无价。

总结

突然发现PG数据库解决MVCC的方式如此独特,不过总的来说都解决了版本控制的逻辑,相比mysql的redo,undo,binlog来解决MVCC而言,每个DB都有自己独特的使用场景。姿势又涨了一丢丢!

引用

【图文详解】MySQL系列之redo log、undo log和binlog详解 [PostgreSQL中文文档]  [PostgreSQL官方文档]

0 人点赞