CMU15-445 数据库系统播客:深入理解多版本并发控制 (MVCC) - 现代数据库的并发基石
MVCC 不仅仅是一个技术术语,它是一种精妙的设计哲学,是现代数据库能够在高并发世界中保持优雅和高效的关键。它通过多版本的“时空”换取了读写并发的“自由”,但其实现背后充满了深刻的权衡。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
构建任何高并发应用时,数据库的性能和稳定性都是我们关注的重中之重。而谈到数据库,一个我们无法绕开的概念就是并发控制。今天,我们将深入探讨现代关系型数据库中最主流的并发控制思想——多版本并发控制(Multi-Version Concurrency Control, MVCC)。
无论是 PostgreSQL, MySQL (InnoDB), Oracle, 还是 SQL Server(在特定隔离级别下),MVCC 都是它们处理高并发读写请求、保证数据一致性的核心机制。
核心思想:MVCC 是什么?
许多初学者可能会误解,认为 MVCC 是一种与两阶段锁(2PL)或乐观并发控制(OCC)并列的并发控制“协议”。但一个更精确的定义是:MVCC 是一种实现并发控制的系统架构(System Architecture)。
它的核心理念极其优雅:通过保留数据的多个历史版本,来实现读写操作的并行不悖。
想象一下传统的锁机制:当一个事务正在写入某行数据时,这行数据就会被“锁”住。此时,其他任何想要读取这行数据的事务都必须等待,直到写事务提交或回滚。同样,一个事务在读取数据时,也可能阻塞其他事务的写入。这种读写相互阻塞的模式,在并发量高的场景下会严重影响系统吞吐量。
MVCC 则彻底改变了这一游戏规则。它的核心承诺是:
写操作不阻塞读操作,读操作也不阻塞写操作。
这是如何实现的呢?当一个事务需要读取数据时,系统不会直接返回“最新”的数据,而是为其提供一个“一致性快照(Consistent Snapshot)”。这个快照代表了该事务启动时,整个数据库的某个一致性状态。如此一来,即使其他事务正在修改数据,当前事务读取的也只是它自己快照中的、未被修改的旧版本,完全不受干扰。
然而,需要明确的是,MVCC 自身并不解决写-写冲突(Write-Write Conflicts)。当两个事务尝试修改同一个数据对象时,系统仍然需要一个仲裁机制来决定谁胜谁负。这时,MVCC 架构就需要与一个传统的并发控制协议(如时间戳排序、乐观锁或两阶段锁)相结合,来处理这类冲突。
历史回眸:一个经久不衰的理念
MVCC 的思想并非凭空出现。它的理论雏形最早可以追溯到1978 年麻省理工学院(MIT)的一篇博士论文。但真正将其工程化并推向市场的,是 20 世纪 80 年代早期的DEC 公司。
DEC 公司开发的两款数据库产品——Rdb/VMS和InterBase,是商业上最早成功实现 MVCC 的系统。这背后的关键人物是Jim Starkey,他不仅是 MVCC 的早期实践者,还被认为是 BLOBs(二进制大对象)和数据库触发器的发明人。
这段历史还有一些有趣的后续:
DEC 的 Rdb/VMS 最终被Oracle收购,成为了 Oracle Rdb。InterBase 在几经易手后被开源,演变成了今天我们所熟知的Firebird数据库。一个有趣的花絮是:Mozilla 最初想将浏览器命名为 Phoenix,因版权冲突改为 Firebird,但再次与 Firebird 数据库重名,最终才定名为我们熟悉的 Firefox。
MVCC 的两大杀手级优势
为什么 MVCC 能够经受住时间的考验,成为现代数据库的标配?因为它带来了两个无与伦比的优势:
极致高效的只读事务
在 MVCC 架构下,只读事务的执行速度快得惊人。当一个事务被声明为只读(Read-Only)时,数据库系统知道它绝不会修改数据。因此,系统无需为其获取任何锁,也无需追踪其读写集。它要做的仅仅是读取其事务开始时那个“一致性快照”。这几乎是零成本的并发,极大地提升了读多写少场景下的系统性能。
强大的“时间旅行”查询能力
由于 MVCC 天然地保存了数据的历史版本,它为实现“时间旅行查询(Time-Travel Queries)”提供了可能。这意味着你可以向数据库提出这样的问题:
“三天前,我们的用户表是什么状态?”“上个季度末,公司的总销售额是多少?”这个概念最早由Postgres在 20 世纪 80 年代提出。但遗憾的是,除了特定领域的数据库外,大多数通用数据库系统并没有完全开放这个功能。主要原因在于成本:支持任意时间点的查询意味着必须永久保留所有历史版本,这将导致存储空间的无限膨胀。
尽管如此,在某些对数据审计和历史追溯有强需求的领域(如金融行业),时间旅行查询的价值巨大。法规可能要求金融机构保留长达数年的交易记录,MVCC 使得在这海量历史数据中进行查询变得异常高效。
深入实现:MVCC 的四大核心设计决策
实现一个健壮高效的 MVCC 系统,远比听起来复杂。数据库开发者必须在四个关键领域做出精心的设计和权衡。
并发控制协议 (Concurrency Control Protocol)
如前所述,MVCC 架构需要一个“伙伴”协议来处理写-写冲突。不同的数据库选择了不同的方案:
时间戳排序 (Timestamp Ordering, T/O): 每个事务获取一个时间戳,系统根据时间戳的先后顺序来决定事务的执行次序。乐观并发控制 (Optimistic Concurrency Control, OCC): 事务执行期间不做任何检查,直到提交时才检查是否存在冲突。如果冲突,则回滚。两阶段锁 (Two-Phase Locking, 2PL): 仍然使用锁来解决写-写冲突,但读操作不受影响。这个协议的选择,直接决定了数据库在不同冲突场景下的行为和性能。
版本存储 (Version Storage)
这是 MVCC 实现中最核心的部分:如何组织和存储一个逻辑数据的多个物理版本?通常,系统会为每个逻辑元组(Logical Tuple)维护一个版本链(Version Chain),而索引指向这个链的“头部”。
主要有三种主流方案:
仅追加存储 (Append-Only Storage)
这是最直观的方式,也是PostgreSQL采用的策略。当一个元组被更新时,系统不会在原地修改它,而是:
将旧版本的完整内容复制一份,形成一个新的物理元组。在这个新的物理元组上执行修改。将版本链的指针指向这个新版本。这种方式又引申出版本链的组织顺序问题:
从旧到新 (Oldest-to-Newest, O2N): 新版本追加在链表的末尾。优点是追加操作简单;缺点是查找最新版本时可能需要遍历整个链。从新到旧 (Newest-to-Oldest, N2O): 新版本放在链表的头部。优点是查找最新版本非常快(O(1));缺点是每次创建新版本,所有指向旧头部的索引都必须更新为指向新头部,更新开销较大。权衡分析:仅追加存储的读取旧版本性能极好,因为旧元组是完整独立的,无需任何计算。但其写入开销较大,即使只修改一个字段,也需要复制整个元组。
时间旅行存储 (Time-Travel Storage)
这种方案将主表和历史表分开。主表上永远只保存最新版本的数据。当数据被更新时,旧版本被复制到一个独立的“时间旅行表”中。这种方式逻辑清晰,但可能增加数据管理的复杂性。
Delta 存储 (Delta Storage)
这是MySQL (InnoDB)和Oracle采用的策略。其思想类似于 Git 的diff:不存储完整的旧版本,而只记录被修改字段的“增量(Delta)”。
当一个元组被更新时,系统会将修改前的“旧值”存放到一个独立的 Delta 存储区(在 InnoDB 中称为 Rollback Segment),然后在主表上进行原地更新。版本链实际上是通过指针串联起来的 Delta 记录。
权衡分析:Delta 存储的写入性能通常更高,因为只需复制少量修改的字段,而非整个元组。但其读取旧版本的开销更大,因为需要从最新版本开始,通过“重放(Replay)”一系列的 Delta 记录来逐步回溯,才能重建出目标历史版本。
性能对决的根源:正是由于版本存储策略的根本不同,我们经常观察到:在读密集型,特别是需要读取历史数据的分析场景下,PostgreSQL可能表现更优;而在写密集型,特别是更新操作频繁的 OLTP 场景下,MySQL (InnoDB)可能更具优势。
垃圾回收 (Garbage Collection)
随着系统运行,会产生大量不再需要的旧版本(例如,所有活跃事务都无法再看到它们,或者由已中止事务创建的版本)。垃圾回收机制(常被称为VACUUM)负责清理这些“垃圾”以回收磁盘空间。
何时可以回收?一个版本可以被安全回收的条件是:当前系统中没有任何一个活跃事务的“快照时间戳”会落在该版本的生命周期内。
如何高效回收?
后台清扫 (Background Vacuuming): 由一个或多个专用后台线程定期扫描表,查找并清理无效版本。为了避免全表扫描的巨大开销,系统通常会维护一个“脏页位图(Dirty Page Bitmap)”,只检查那些被修改过的数据页。这是PostgreSQL的主要方式。协作式清理 (Cooperative Cleaning): 工作线程在执行查询、遍历版本链的过程中,“顺手”清理掉它们遇到的无效旧版本。这种方式非常巧妙,但它仅适用于从旧到新(O2N)的版本链,因为只有在这种顺序下,查找新版本才会自然地经过旧版本。索引管理 (Index Management)
在 MVCC 中,索引不仅要能找到数据,还要能找到正确版本的数据。
主键索引 (Primary Key Indexes): 通常比较直接,索引条目直接指向版本链的头部。如果主键本身被更新,系统通常将其处理为一次DELETE+ 一次INSERT。
二级索引 (Secondary Indexes): 处理起来要复杂得多,直接影响数据库的性能特征。
物理指针 (Physical Pointers): 二级索引的叶子节点直接存储元组的物理地址(如:页ID + 页内偏移量)。这是PostgreSQL的典型实现。a.优点: 查询速度快。通过二级索引能一次性定位到数据,无需额外查找。
b.缺点: 更新开销大。在 PostgreSQL 的仅追加模型中,一次UPDATE会导致元组产生新的物理位置。这意味着,所有指向该元组的二级索引(可能有很多个)都必须被更新,这在写密集型负载下会成为巨大的性能瓶颈。
a.优点: 更新开销小。当元组更新(即使物理位置改变)时,只要主键不变,所有二级索引都无需改动。这极大地提升了写入性能。
b.缺点: 查询需要“回表”。通过二级索引查找时,只能先找到主键值,然后必须再回到主键索引(聚簇索引)中进行第二次查找,才能定位到最终的数据。这增加了一次额外的索引查找开销。
总结
MVCC 不仅仅是一个技术术语,它是一种精妙的设计哲学,是现代数据库能够在高并发世界中保持优雅和高效的关键。它通过多版本的“时空”换取了读写并发的“自由”,但其实现背后充满了深刻的权衡。
从版本存储(Append-Only vs. Delta)到索引管理(物理指针 vs. 逻辑指针),每一个设计决策都直接影响了数据库(如 PostgreSQL 和 MySQL)在不同应用场景下的性能表现。理解这些内在机制,不仅能帮助我们更好地选择和使用数据库,更能让我们在进行系统设计和性能优化时,做到胸有成竹,游刃有余。
相关攻略
在 OpenClaw 的 AI 生态系统中,要使人工智能从“思考分析”转变为“实际操作”,技能(Skill)扮演着至关重要的桥梁角色。简而言之,技能是 AI 执行特定任务的模块化能力单元。这些模块主要来源于两大渠道:一是生态内可直接安装使用的成熟社区技能,二是用户根据个性化需求,自行开发的定制化技能
Prometheus对于不同的数据库,有各种专门的Exporter进行监控,本文将介绍基于Prometheus监控postgresql数据库的解决方案。 Postgresql数据库是一款热门的开源关
在之前的文章中,举了一个强制类型转换导致死锁的例子,有朋友询问是不是类型转换都不能命中索引,花1分钟细说一下。 《两个小公举,调试MySQL死锁必备!》中,举了一个强制类型转换导致死锁的例子,有朋友
SQL Server的查询计划全靠统计信息“指路”,一旦统计信息过期,数据库就会“瞎猜”数据分布,要么生成低效查询计划,要么计数失真,堪称DBA的“隐形坑”。 明明SQL没写错,count(*)时而
3月1日消息,国家安全部最新发文,提醒企业对于数据托管切莫“托而不管”,并特别提到了境外黑客攻击某电商平台数据库的案例。如今,不少企业选择将数据存储在数据托管平台,降本增效,省心省力,但这也潜藏着威
热门专题
热门推荐
微软战略转向:Win11内置应用将全面重构,告别网页套壳以提升性能 你是否感觉Windows 11某些应用响应迟缓,或是内存占用异常偏高?最新消息或许值得关注。据官方透露,微软正调整其应用开发战略,将逐步减少对网页技术的依赖,转而启动大规模原生应用重构计划。这一重大决策,标志着此前推广的“网页化”开
《红色沙漠》全支线任务图文攻略与深度解析 在开放世界大作《红色沙漠》中,丰富多样的支线任务是游戏体验不可或缺的一环。许多玩家初次接触时,可能会对任务系统感到困惑。实际上,每个支线都有其独特的设计思路与完成技巧。例如任务“图尔纳里的请求”,其核心玩法侧重于资源收集与体力劳动,你需要按照指示完成特定的伐
知名破解组织宣布成功突破《EA Sports FC 26》四重防护系统 近日,游戏安全领域传来重磅消息:因屡次攻破高级加密而声名鹊起的破译团队DenuvOwO,正式对外宣告已成功放出针对《EA Sports FC 26》的最新破解方案。该方案直接破解了游戏核心的Denuvo虚拟机加密技术,一石激起千
快速部署指南:基于DeepSeek与飞书的Ubuntu虚拟机镜像,30分钟完成私有AI助手搭建 你是否希望在本地快速搭建一个集成DeepSeek大模型能力、并能通过飞书机器人便捷调用的AI开发环境?我们提供的基于WSL2的Ubuntu预配置虚拟机镜像,正是为你量身打造的“一站式AI应用解决方案”。本
《绝地求生》全新“物品狩猎”躲猫猫模式正式上线:玩法宣传片深度解析 《绝地求生》重磅更新,备受期待的趣味玩法“物品狩猎”模式现已正式推出。这一全新的躲猫猫玩法究竟有何独特之处?官方已发布完整版宣传视频,为玩家们详细揭秘核心规则与对战策略。想要抢先了解新模式的玩家,可以通过本文的介绍一探究竟。 最新发





