MVCC
Multi-Version Concurrency Control
处理并发访问和事务隔离
原理:每个数据库记录维护多个版本,通过使用版本号或时间戳来跟踪和管理事务对数据的读写操作
并发控制机制
并发可能产生的问题
脏读(Dirty Read):当一个事务读取了另一个事务尚未提交的数据时,称为脏读。如果未提交的事务被回滚,那么读取到的数据实际上是无效的数据,可能导致错误的结果。
不可重复读(Non-repeatable Read):在一个事务中,多次读取同一数据,但在事务执行期间,其他事务修改了该数据并提交。这样,每次读取同一数据时,得到的结果可能不一致,导致不可重复读。
幻读(Phantom Read):在一个事务中,多次执行相同的查询,但在事务执行期间,其他事务插入、更新或删除了符合查询条件的数据,导致每次查询得到的数据集不同,出现幻读现象。
丢失更新(Lost Update):当两个或多个事务同时对同一数据进行写操作时,最后提交的事务可能会覆盖先前提交的事务所做的修改,导致部分更新丢失。
死锁(Deadlock):多个事务之间发生循环等待资源的情况,导致事务无法继续执行。如果不处理死锁,系统可能陷入无限等待状态。
不可重复读与幻读
不可重复读(Non-repeatable Read):不可重复读指的是在同一个事务中,多次读取同一数据,但在事务执行期间,其他事务修改了该数据并提交。结果是,每次读取同一数据时,得到的结果可能不一致。这是因为事务在执行过程中,其他并发事务修改了数据,导致读取到的数据不一致。不可重复读的重点在于数据的一致性,同一个事务内读取到的数据发生了变化。
幻读(Phantom Read):幻读指的是在同一个事务中,多次执行相同的查询,但在事务执行期间,其他事务插入、更新或删除了符合查询条件的数据。结果是,每次执行相同的查询时,得到的数据集可能不同。幻读的重点在于数据集的一致性,同一个事务内查询到的数据集发生了变化。
关键区别:
内容:不可重复读关注的是同一个事务内读取同一数据时数据内容的变化,而幻读关注的是同一个事务内执行相同查询时返回的数据集的变化。
引起的原因:不可重复读通常是由于其他事务对数据进行了修改并提交,而幻读通常是由于其他 事务对符合查询条件的数据进行了插入、更新或删除操作。
控制机制:不可重复读可以通过锁机制或 MVCC 等并发控制机制来解决,而幻读通常需要使用更高级别的隔离级别(如串行化隔离级别)或其他控制机制来避免。
锁
在传统的并发控制机制中,如锁定机制(Locking),读操作和写操作会相互阻塞,导致并发性能下降。
MVCC
更加乐观的并发控制策略,通过使用版本号或时间戳来跟踪和管理事务对数据的读写操作。
MVCC 核心概念
每个数据库记录(或数据行)都有多个版本,每个版本都有一个唯一的标识符或时间戳。当事务开始时,它会读取一致性视图(Consistent Snapshot)中的数据版本,并在该事务执行期间保持不变。这样,在事务执行期间其他事务对数据的修改不会对当前事务造成干扰。
MVCC 关键组件
版本号或时间戳:每个数据记录都会包含一个版本号或时间戳,用于标识该数据记录的不同版本。
可见性检查:在读操作时,事务会根据其开始时间戳或数据记录的版本号来判断是否可以看到该记录。一般情况下,事务只能看到开始时间戳早于或等于其自身时间戳的数据记录。这意味着事务只能看到在其开始之前或同时进行的写操作所创建的数据版本。
冲突检测与回滚:当多个事务同时修改相同的数据记录时,可能会发生冲突。MVCC 使用乐观并发控制策略,即假设冲突很少发生。如果发生冲突,数据库管理系统会检测到并回滚相关事务,以确保数据的一致性。
垃圾回收:当事务完成后,数据库会清理无效的旧版本数据,以释放存储空间。
MySQL InnoDB 引擎中 MVCC 的实现
版本号和回滚指针:InnoDB 引擎使用两种类型的版本号来实现 MVCC。每个数据行都有一个隐藏的版本号,"transaction ID"(事务 ID)。此外,每个事务都有一个回滚指针,指向事务开始时数据库中的一个一致性快照(链表)。
快照读(Snapshot Read):在 MVCC 中,读操作可以在不加锁的情况下进行。当事务开始时,InnoDB 会记录一个一致性读视图(Consistent Read View),表示该事务读取数据的时间点。一致性读视图是通过事务的回滚指针和其他活跃事务的版本号来确定的。
数据记录版本管理:当事务进行写操作时,InnoDB 引擎会为新插入或更新的数据行创建一个新版本,并将该版本的事务 ID 设置为当前事务的 ID。旧版本的数据行会保留在数据库中,以满足正在执行的事务的一致性读需求。
可见性判断:在执行读操作时,InnoDB 引擎会根据事务的一致性读视图和数据行的版本号来判断数据行是否可见。如果数据行的版本号大于事务的回滚指针,则该数据行对该事务不可见。
并发事务冲突处理:当多个事务同时修改同一数据行时,可能会出现冲突。InnoDB 使用锁机制来处理并发事务冲突。当事务需要修改某个数据行时,如果该数据行的版本号大于当前事务的回滚指针,则需要获取相应的锁。如果无法获取锁,则事务会等待,直到锁可用。
事务回滚和垃圾回收:当事务回滚时,InnoDB 会根据事务的回滚指针将该事务创建的新版本数据行标记为无效,并将旧版本的数据行重新设为可见。垃圾回收(Garbage Collection)过程会定期清理和回收无效的旧版本数据行,以释放存储空间和维护数据库性能。
RC(读已提交)和 RR(可重复读)
RC:每次查询开始新建一致性读视图,从而更新试图中事务 ID 链表。
RR:第一次查询新建一致性读试图,事务结束前复用当前视图,事务 ID 链表中数据未变化,从而保证可重复读。
PostgreSQL 引擎中 MVCC 的实现
事务 ID(XID):PostgreSQL 使用事务 ID(Transaction ID)来标识每个事务的唯一标识符。事务 ID 是一个递增的全局计数器,用于识别不同事务之间的先后顺序。
版本链:在 MVCC 中,每个数据行都可以有多个版本,每个版本都有一个唯一的事务 ID。PostgreSQL 使用版本链(Version Chain)来管理数据行的不同版本。每个版本都包含了一个指向上一个版本的指针和创建该版本的事务 ID。
可见性信息:PostgreSQL 维护了一组可见性信息,用于判断每个事务对数据行的可见性。可见性信息包括事务的启动时间戳、提交时间戳和删除信息。
快照读(Snapshot Read):在 MVCC 中,读操作可以在不加锁的情况下进行。当事务开始时,PostgreSQL 会为该事务创建一个快照(Snapshot),即该事务的一致性视图。快照包含了事务开始时的可见性信息,用于确定读操作可以看到哪些数据版本。
可见性判断:在执行读操作时,PostgreSQL 会根据事务的快照和数据行的版本链来判断数据行是否对该事务可见。如果数据行的版本链中存在一个版本的事务 ID 小于等于事务的启动时间戳并且该版本未被后续事务删除,则该数据行对该事务可见。
并发事务冲突处理:当多个事务同时修改同一数据行时,可能会出现冲突。PostgreSQL 使用锁机制和冲突检测来处理并发事务冲突。它使用行级锁和事务级锁来保护数据的一致性,并使用乐观并发控制来检测冲突并协调事务的执行。
版权声明: 本文为 InfoQ 作者【陈皮】的原创文章。
原文链接:【http://xie.infoq.cn/article/ecca853114b10b65b6e25e8e5】。文章转载请联系作者。
评论