2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

PostgreSQL 和 InnoDB 的多版本实现原理和比较

  • 2009-11-12
  • 本文字数:2343 字

    阅读完需:约 8 分钟

在风清扬的博客多版本并发控制:PostgreSQL vs InnoDB 中很好的阐述了 PostgreSQL 和 InnoDB 的多版本并发控制原理,并根据自己的丰富经验进行了深入的对比。

多版本并发控制技术被很多数据库或存储引擎采用,如 Oracle,MS SQL Server 2005+, PostgreSQL, Firebird, InnoDB, Falcon, PBXT, Maria 等等。新的数据库存储引擎,几乎毫无例外的使用多版本而不是单版本加锁的方法实现并发控制,可以说多版本已经成为未来的发展趋势。

PostgreSQL 的多版本实现原理 (基于 8.4.1 版本)

PostgreSQL 采用堆 +B+ 树索引(忽视 R 树、哈希、GiST 等不常用的索引)的存储结构,堆与索引的存储模式不同。

PostgreSQL 里记录的新老版本都存在堆里,堆中每条记录 header 里都通过 t_xmin 和 t_xmax 来存储创建事务 ID(creation transaction id)和销毁事务 ID(destruction transaction id)。在他的另一篇博客从对数据访问扭曲的适应性评价 PostgreSQL 与 InnoDB 里很简洁地介绍了这个实现策略:

PostgreSQL 处理多版本的简单逻辑是这样的:一个事务更新一个记录时产生一个新版本,并设置新版本的产生事务 ID 和和原版本的消亡事务 ID 为该事务 ID。新旧版本除被组织成一个链表外,其物理存储没有任何其它联系,就好像是两个记录一般。事务在读取到一个版本时,通过这两个事务 ID 与当前事务 ID 之间的关系来判断它是否应该看到这个版本。这一实现策略相当简洁(对索引的多版本存储尤其是如此),通过索引访问时更不会有对回滚段的额外访问,可以消除事务回滚的代价。

更详细的数据结构和函数可以参考 Inside PostgreSQL Shared Memory 等文章。除此之外,事务提交日志和事务快照也被用来进一步保存版本的信息,包括事务的状态和当时活跃事务的列表等。而对于 PostgreSQL,其索引是没有版本信息的。 通常更新每一条记录都会在该记录所在表的所有索引中插入相应的索引项。他在文中说这样会导致进行索引扫描时,即使查询所需所有属性在索引中都存在,也需要从堆中取出对应的记录判断是否可见除了:

在 PostgreSQL 8.3 中引入了 HOT(Heap-Only-Tuple)技术,如果新老版本在同一页面,并且 UPDATE 没有更新任何索引属性,则不插入新版本对应的索引项。

对于事务的提交和回滚操作,风清扬认为:

事务提交或回滚时操作简单,除事务提交时要写出事务外,只需要更新事务提交日志中对应的事务状态。也就是说回滚时并不需要将事务所作的操作从物理上清理掉,只要将事务状态设为已经回滚,则该事务产生的版本对其它事务自然就不可见了。

他也给出了不需要的老旧版本的不可见处理方式:

老旧的不再需要的版本,即不会被将来的任何事务见到的版本的清理是通过 VACUUM 实现的。由于新老版本混杂在一起,进行 VACUUM 时本质上是需要扫描所有数据。8.4 版中引入了 Visibility Map 技术,用来在 VACUUM 时跳过那些肯定不包含老旧版本的页面,但如果系统更新频繁且离散,这一技术就派不上大用场。在线的 VACUUM 只能清理页面中的老旧版本,但不能缩减表占用的空间,其实是产生碎片。要缩减表空间时的 VACUUM 会锁住表导致期间表不能被更新。

InnoDB 的多版本实现(基于 MySQL 5.1.33 版本带的 InnoDB)

InnoDB 采用索引组织表的存储结构,没有堆,记录存储在主键索引中,其它索引称为二级索引,其中每个索引项都包含所对应记录的主键。主键索引与二级索引的存储格式也不同。

InnoDB 的主键索引拥有版本化信息,除了主键被更新的情况需要存储多个版本,其他情况主键索引中只存储记录的最新版本,把旧版本的信息则集中存储在回滚段中:

主键索引记录的头上包含有 6 字节的事务 ID 与 7 字节指向回滚段中旧版本的指针。DELETE 时只是标记而不真正删除。UPDATE 时进行本地更新,并将前像写到回滚段中。

InnoDB 中读视图(和 PostgreSQL 中事务快照类似),也记录了事务开始时的活跃事务列表:

根据读视图和记录头上的事务 ID,可以判断出一个版本在事务开始时是否已经提交,即是否可见。如果存储在主键索引中的记录不可见,则根据指向回滚段中旧版本的指针找到旧版本信息,构造出旧的记录。回滚段采用的是 append-only 的日志型存储,记录的旧版本信息并不是一条完整的记录,而只是被更新的属性的前像。回滚段中的旧版本信息中也包含更旧的版本的位置,即版本链表是从新到旧的。

对于 InnoDB 的事务处理:

由于没有事务日志表示事务是否回滚,在事务回滚时必须清理该事务所进行的修改,插入的记录要删除,更新的记录要更新回来。事务提交时则无需处理。

如前所说,InnoDB 的二级索引中的每个索引项并没有版本化信息:

但在页面头记录了对该页面操作的事务的 ID 的最大值,通过这一值可以判断页面中是否可能包含不可见的数据,如果是,则需要访问主键索引判断可见性。否则,可以直接从索引中获取查询所需属性。二级索引中可能存储一条记录的多个索引对应的索引项,如果 UPDATE 操作更新了某个索引的属性,则类似于 PostgreSQL,插入新索引项到二级索引中,老索引项并不删除。但没有被 UPDATE 操作更新的索引则不需要插入新索引项。

在文章的最后,作者分享了他个人对这两种实现方式的评价,优势为:

PostgreSQL 与 InnoDB 的多版本实现最大的区别在于最新版本和历史版本是否分离存储,PostgreSQL 不分,InnoDB 分。

相对于 InnoDB,PostgreSQL 的优势似乎主要的只有一条:事务回滚可以立即完成,无论事务进行了多少操作。查询以前的历史数据的功能并不常用,在目前的 PostgreSQL 中也并不实用。

劣势为:

InnoDB 的主要劣势在于事务回滚时需要清理事务所作的所有修改,因此使用 InnoDB 时要避免使用超大型事务,否则回滚可能超慢无比。

而 PostgreSQL 的主要劣势在于清理老版本的扫描代价,索引属性更新时引起的索引项频繁插入,堆空间的回收问题以及判断算法的复杂性和开销等。更深入的介绍请参考风清扬博客。

2009-11-12 23:414060
用户头像

发布了 42 篇内容, 共 18.9 次阅读, 收获喜欢 6 次。

关注

评论

发布
暂无评论
发现更多内容

JavaScript 数组展平方法: flat() 和 flatMap()

devpoint

11月日更 flat flatMap

在线文本按列截取工具

入门小站

工具

架构实战营-模块六

瓜子葫芦侠

「架构实战营」

【高并发】浅谈AQS中的CountDownLatch、Semaphore与CyclicBarrier

冰河

Java 并发编程 多线程 高并发 异步编程

DPDK 网络协议栈-vpp-OvS-DDos-虚拟化专家之路

赖猫

Linux 网络协议栈 DPDK

风口上的低代码:我们看到了这些变化与趋势

脑极体

央视新闻联合百度智能云打造首个AI手语主播

百度大脑

人工智能

【死磕Java并发】-----J.U.C之AQS:阻塞和唤醒线程

chenssy

11月日更 死磕 Java 死磕 Java 并发

如何使用find和locate 命令在Linux 中查找文件和目录?

Ethereal

Linux find locate

Prometheus Exporter (十)Ping Exporter

耳东@Erdong

Prometheus ping exporter 11月日更

[Pulsar] 消息生命历程(四)——Broker的消息处理

Zike Yang

Apache Pulsar 11月日更

详解阿里开源分布式事务框架Seata

博文视点Broadview

架构原则

卢卡多多

架构 11月日更

小谈C#异常

喵叔

11月日更

博文推荐 | 基于 Pulsar 事务实现 Exactly-Once 语义

Apache Pulsar

Java 架构 云原生 中间件 Apache Pulsar

博文推荐|深度解析如何在 Pulsar 中实现隔离

Apache Pulsar

Java 架构 分布式 云原生 Apache Pulsar

程序员如何应对职业天花板

石云升

职场天花板 职场经验 11月日更

linux 上查找包含特定文本的所有文件

入门小站

Linux

贪心算法:加油站 ⛽

空城机

算法 大前端 11月日更

豪华阵容!13位专家力荐Spring5为企业级开发提供一站式方案

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

一物一码可追溯!看区块链如何帮助消费者

CECBC

头条观察 | 元宇宙成为必然趋势的三种可能

CECBC

Android C++系列:Linux进程间通信(二)

轻口味

c++ android jni 11月日更

Java Collectors API实践

FunTester

Java API 测试开发 FunTester Collectors

《深入理解JVM虚拟机》读书笔记:第二章 Java内存区域与内存溢出异常

Joseph295

没有人比我更会使用集合!对,是dart中的集合

程序那些事

Java flutter dart 程序那些事 11月日更

声网下一代视频引擎架构探索与实践

声网

音视频 视频处理 RTE 技术详解

docker vs docker-compose 解密

liuzhen007

11月日更

搞定大厂算法面试之leetcode精讲6.深度优先&广度优先

全栈潇晨

算法面试 Leet Code

Flutter:如何在没有插件的情况下制作旋转动画

坚果

flutter 11月日更

译文 | Apache Pulsar 集群如何确保消息不丢

Apache Pulsar

Java 分布式 云原生 中间件 Apache Pulsar

PostgreSQL和InnoDB的多版本实现原理和比较_数据库_晁晓娟_InfoQ精选文章