为什么PostgreSQL的回滚是瞬间完成的?

2021-01-28 15:37:37 浏览数 (1)

pg数据库的回滚是瞬间完成的。看到这句话是不是觉得pg很先进,确实是这样,但是也是有代价的,下面聊一聊这个问题。

事务的回滚和数据库的MVCC机制是分不开的,先看看以oracle和mysql为代表的基于undo表空间实现的mvcc。以mysql为例,mysql undo两个最重要的功能,一个是实现了写不阻塞读,或者说是mvcc,另外一个当然就是事务的回滚。

MySQL在进行数据操作时,先将数据备份到undo段中,然后再进行数据的修改,这样未提交的数据会保存一份前镜像在undo中,同时数据行上的rollpointer指针指向undo段上的老数据,同时老数据由于有可能经过多次更新,所以具有多个版本,多个数据版本之间通过链表链接。数据库如果查询正在更改的数据,会通过指针查到undo中的前镜像,这样就实现了读写的互不阻塞。需要回滚时,数据库会使用undo的旧数据恢复回来,所以基于undo的回滚是有一个过程的,需要将数据反向操作到原来的状态,这个反向操作可能对于数据库是灾难性的,同时undo也是会产生redo的。

所以对于undo实现的mvcc来说长事务或者大事务可能对数据库产生灾难的影响,如果一个事务长期不提交,那么大量的undo数据将一直保留,而且不能被清空覆盖。另外一个是跑批,大批量的更新如果被异常中断,那么回滚需要将这个大操作反向再做一次,对数据库的消耗是非常大的。当然mysql对于delete做了一定优化,delete只是打了标识位。

再说说pg的回滚,pg的回滚是瞬间完成的,这个是为什么呢?我们知道pg没有undo表空间,通过将多版本的数据真实存储在数据页里来实现mvcc,读取一条未提交数据行会去读取以前的数据版本,而以前的数据版本不是存在于undo而是和真实数据一起存放在数据页中,过期的元组会不定期进行清理,释放空间。

不敢说pg的数据多版本和mysql的undo孰优孰劣,对于最大的诟病就是空间的膨胀,过期的数据页需要不定期清理,但是反过来想,如果把旧版本放在undo里岂不也是一样在事务提交后需要清理,而且undo限制死了最大使用的undo段,这在数据库的设计上我觉得不是优美的。而pg这样设计的好处也非常明显,就是显著提高了dml的性能,因为在做更新类操作时,不需要去写undo,旧的数据就在原地不动,我只需要插入新的数据,这样性能非常好,测试过pg的更新的性能能够达到20w条每秒,这是一个非常恐怖的性能数据。

所以对于pg来说,插入就是插入元组并改xmin值,更新也是插入元组并更改xmin,xmax值,删除只是改xmax值。知道了pg的多版本原理,再看看pg的回滚就很好理解了,比如说我正在做一个1G大小的表的全表update更新,我们会在更新的过程中看到表的大小一直在变大,更新完了之后表的大小会变成原来的正好2倍,这就是因为老版本的数据并没有当时删除,而是插入了更新后的值。那么现在比如在更新过程中比如500M的地方将语句杀掉,那么可以看到这个表的大小停留在1.5G大小,也就是说已经插入的500M的数据不需要当时就清理掉的,不需要挨个回滚的。这也就是为什么pg的回滚很快的原因。如果这时做个vacuum full这个表又会恢复到1G大小。

0 人点赞