干货|分析 PostgreSql 单表 60w 数据却占用 55g 空间
突然听到运维说磁盘预发布环境磁盘空间不够,细查之下发现是由于某个表的数据太大导致的,但是查看了下数据库表发现,实际的表数据量只有 60w 条,很明显表哪里出问题了,一开始以为是犹豫表的设计不合理索引导致的数据量大,细看之下发现挺正常的。正在焦虑蹉跎的时候,有幸得到朋友圈大佬的指点,是死亡元组太多导致的只需要执行 vacuum full 清理死亡元组就好,查看了相关的博客稳定发现 postgresql 居然会保存 mvcc 多版本修改记录,简单理解就是,postgresql 对你所做的修改和删除都会保存记录,不会清理释放空间。这让我顿时想到Mysql的MVCC,但是 mysql 的 undo log 也只记录执行操作的相反记录保留最新的记录,而 redo log 记录数据页的变更,但是大小是固定的,都可以通过配置参数配置固定大小。
单表超过 55g 实际数据却只有 60w 条
回到 postgresql 数据库上,一开始提到的死亡元组问题提到了VACUUM命令 简单了解之下只是看到一些博客说 pg 会保留更新删除数据行的 MVCC 版本记录数据,完了又看到官网的解释:
简单的 VACUUM(不带FULL)简单地收回空间并使其可以被重用。
敲黑板画重被重用,标记为垃圾空间方便重用,就好像 jvm 标记了垃圾,但是不清理,只是后面会被覆盖,这时我就觉得很扯,为什么不清理,这么一直留着只会表的数据越来越大只增不减,而且还可能误读,感觉好鸡肋啊,紧接着继续研究为什么会产生这么多空间,虽然细细的研究之下我发现官网一句特别的提示:
24.1.2. 恢复磁盘空间 在PostgreSQL中,一次行的UPDATE或DELETE不会立即移除该行的旧版本。这种方法对于从多版本并发控制(MVCC,见第 13 章)获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行VACUUM来完成。
或许标记重用只是为了保留之后可以回溯历史,方便事务记录查询,详细见24.1.2. 恢复磁盘空间这就解释了为什么一个表明明只有 60w 数据却空间占用 55g,一条记录被更新之后他的快照依然会保留,不会立刻删除,当更新或者删除特别频繁的时候,空间占用就会特别的明显了,vacuum 命令类似于标记一些过时的数据为垃圾数据(这有点像操作系统,当你的把数据删除了,其实他只是标记删除,完了继续堆积在新的未存放数据的空间,这就是说为什么理论上,不存在彻底的删除,除非你把磁盘填满之后重新覆盖),可以被之后的新记录覆盖,对于急着释放空间页面请求又不是特别多的情况下还是需要 vacuum full 来紧急释放空间,另外官网也不建议频繁的 vacuum full 来代替 vacuum 毕竟,况且使用 vacuum full 会锁住整个表,之前的预发布环境中,也整整执行了 6 分钟,这是非常不理想的,万一用户在使用呢这就很不友好了,况且版本记录有时候还是有用的。
注意
其实官网也不建议频繁的使用 vacuum full 来清理死亡元祖,除非需要立刻释放空间,毕竟数据无价。
总结
突然发现 PG 数据库解决 MVCC 的方式如此独特,不过总的来说都解决了版本控制的逻辑,相比 mysql 的 redo,undo,binlog 来解决 MVCC 而言,每个 DB 都有自己独特的使用场景。姿势又涨了一丢丢!
评论