嘿呦喂!一文了解Mysql的MVCC机制!

MVCC简介 

MVCC (多版本并发控制,Multi-Version Concurrency Control) 是一种流行的数据库并发控制技术。它允许多个事务在同一时间进行,而不会相互阻塞,从而增加了系统的吞吐量

1. 设计原则

  • 多版本:每次更新数据时,MVCC不是直接修改原始数据,而是为该数据创建一个新版本。旧版本的数据被保留以服务那些开始于更新之前的事务。
  • 读不锁,写不阻塞:MVCC允许读和写操作在没有显式的锁定的情况下同时进行。

2. 工作原理

  • 事务ID每个正在执行的事务都有一个唯一的事务ID,这个ID是按事务开始的时间顺序分配的。

  • 行版本化:当一个事务想要更新一个数据项时,它不会直接覆盖旧数据。相反,它会创建一个新版本,并将其与该事务的ID关联起来。旧的版本将被保留,并与创建它的事务ID关联。

  • 可见性规则当一个事务想要读取数据时,它会看到当前最新的那个版本。

  • 垃圾收集:随着时间的推移,某些数据的旧版本可能不再被任何事务所需要。这些旧版本可以通过一个后台过程(通常称为垃圾回收)来安全地删除。

3. 优势

  • 高并发:多个事务可以在没有互相阻塞的情况下同时读取数据。
  • 无锁读:读取操作不需要获得锁,因此不会被写操作所阻塞。

4. 挑战

  • 写放大:每次更新都需要存储新版本,可能导致更多的写操作和更大的存储需求。
  • 垃圾回收:需要一个额外的过程来定期清理不再需要的旧版本。

DBA需要知道什么

MVCC(多版本并发控制)在许多数据库系统中是默认启用的,特别是在那些支持事务的关系型数据库中,例如PostgreSQL和MySQL的InnoDB存储引擎。一旦选择了使用支持MVCC的存储引擎或数据库,MVCC的机制就会自动工作,不需要数据库管理员(DBA)进行特殊配置或手动处理。

对于DBA来说,他们需要知道的是:

  1. 理解MVCC的原理和工作机制:这有助于解决潜在的性能问题、锁冲突和事务隔离问题。
  2. 监控和性能调优:虽然MVCC大大增加了并发性,但它也可能带来额外的开销,如存储需求增加(因为要保存多个版本的数据)和增加的I/O操作(因为必须清理旧版本的数据)。因此,DBA可能需要监控数据库的性能,确保资源被合理利用,并在必要时进行优化。
  3. 垃圾收集和版本清理:虽然许多现代数据库系统会自动进行旧版本数据的清理,但在某些情况下,DBA可能需要了解或介入这个过程,例如调整清理频率或手动触发清理操作。

MVCC的垃圾回收机制

垃圾回收机制在多版本并发控制(MVCC)的上下文中,主要是指清除不再需要的数据版本。在MySQL的InnoDB存储引擎中,这通常涉及到清理被标记为过期的行版本。那么,判定过期的标准是什么呢?

  1. 垃圾回收判定条件

    • 事务的生命周期:一旦一个事务被提交,它所产生的早期行版本可能就不再需要了。但由于可能还有其他长时间运行的事务需要这些早期版本来提供一致的读取视图,所以不能立即删除。
    • Read View:InnoDB为当前运行的所有事务维护了一个“Read View”。只有当没有事务再需要旧版本时,这些旧版本才会被标记为可以回收。所以标准为:没有事务再需要这个版本时,就判定该版本过期。Read View是什么?如何根据它判断?下文会有讲解。
  2. 回收方式

    • Purge Thread:InnoDB有一个内部线程称为Purge Thread,它的工作是删除过期的行版本。Purge操作是后台进行的,通常不会对正在运行的查询产生直接影响。
    • 合并操作:当数据页在更新操作中产生太多的历史版本时,InnoDB可能会触发一个合并操作,将数据页与它的历史版本合并,并清除不再需要的行版本。
  3. 手动调节

    • innodb_purge_threads:设置Purge线程的数量。更多的线程可能会加速清理过程,但也会增加CPU使用。默认值通常就足够了。
    • innodb_purge_batch_size:每次Purge操作删除的行版本数。增大这个值可能会加速清理过程,但也可能增加I/O压力。
    • innodb_max_purge_lag:用于控制Purge操作的延迟,以减少它对其他操作的影响。如果延迟超过这个值,InnoDB会减缓DML操作,给Purge操作更多时间来追上。

当调整这些参数时,关键是要平衡清理操作与其他数据库活动之间的资源使用。过多的清理可能会影响数据库的性能,而不足的清理又可能导致空间浪费和性能下降。在调整参数之前,最好先对当前的数据库性能有一个基准,以便之后比较调整的效果。


Read View!

“Read View”在InnoDB中是一个关键的概念,它确保事务能看到一致性的数据视图,即使并发事务正在修改数据。在MVCC中,每个事务都有一个与之关联的Read View,它决定了该事务可以“看到”哪些行版本。

当我们说“只有当没有事务再需要旧版本时,这些旧版本才会被标记为可以回收”,这里的逻辑是基于以下几点考虑:

1. Active Transactions:InnoDB会跟踪所有当前活动的事务。
2. Transaction IDs:每个事务都有一个唯一的ID。当行数据被修改时,InnoDB会记下哪个事务做了修改,并为该行分配一个新版本。这个新版本的行将会有一个与修改它的事务ID关联的标签。

3. Oldest Active Transaction:这个标识符指向当前所有正在运行的事务中最早启动的那个事务。它的存在非常重要,因为它决定了哪些数据版本是可以被认为是“过时”的,进而可能被垃圾回收。InnoDB始终知道当前所有活动事务中最早启动的事务的ID,即“最旧的活动事务”。当最旧的事务结束(无论是提交还是回滚)后,InnoDB会查找下一个最旧的仍在活动中的事务,并更新该标识符。这样,数据库始终可以跟踪当前所有活动事务中最早启动的事务。

4. Determining Visibility:给定一个行版本和一个事务,InnoDB可以确定该事务是否应该看到这个行版本。规则很简单:如果行版本的事务ID在当前事务之前,并且它在“最旧的活动事务”之后,那么这个行版本对当前事务是可见的。

作者总结一下:就是给所有事务分别建立一个当前可读数据的视图,只要这个数据所在行的事务ID位于当前事务和最早事务之间,这个数据就是可读的。当所有活动的事务的Read View都没有某一个版本的数据时,这个数据就可以被回收了。

因此,当InnoDB尝试确定一个行版本是否仍然需要时,它会查看所有活动的事务。如果一个旧的行版本对所有活动的事务都不可见(即它们都有比这个行版本更新的事务ID),那么这个行版本就可以被回收了。

这个机制确保了不会过早地删除仍然被某个事务需要的行版本,而这正是MVCC要保证的一致性读取视图的核心。


让我们再细化这个过程:

  1. 事务开始时获取Read View当一个事务开始时,MVCC机制会专门给它固定一个Read View,这个视图基于当前所有活动的事务来确定。这意味着,在该事务的整个生命周期内,它能够看到所有那些在该事务开始之前已经提交的事务所做的更改,看到的数据版本是一致的。但它不能看到在它开始后其他并发事务所做的更改,不会受到其他并发事务的影响。相当于事务一执行操作,我们就给它拍了张照片,里面全都是拍照片时活动的事务以及绑定的数据,这个照片的内容直到事务完成之前都不会改变,里面的数据版本都是一致的。

  2. 新事务和旧数据:一旦数据的某个版本被回收,就不可能有新的事务需要这个版本了。因为新的事务会有一个新的Read View,这个Read View是基于更高的事务ID确定的,它不会回溯到已经被回收的旧数据版本。也就是说,一个新事务开始执行,我们继续给它拍照片,但可惜,物是人非,某些事务所绑定的数据已经被回收了,所以不可能出现在拍照片时活动的事务中,也就不可能被新事务所发现并使用。

  3. 新事务的Read View基于一个比所有当前活动事务都更高的事务ID来创建。因此,新事务永远不会需要一个已经被标记为过时并被垃圾回收的数据版本。简单地说,新事务始终“向前看”,而不是“向后看”


作者的疑问

学习到这里,作者产生了疑问:

数据只有在所有活动事务的Read View都不再展示它时才会被回收。会不会出现事务太多,导致存在事务没有来得及进入活动队列中的情况呢?这样会不会出现回收之后,新事务才开始活动,但是需要刚刚回收的数据呢?

答:这种情况不太可能在经典的RDBMS如MySQL的InnoDB存储引擎中出现。这是因为事务的管理和调度是数据库的核心功能,事务的生命周期和状态都是严格管理的。当事务开始时,它会立即被分配一个唯一的事务ID,并进入活动事务列表。以下是一些关键点:

  1. 事务ID:在InnoDB中,每个事务都会在开始时被分配一个唯一的递增的事务ID。这确保了每个事务都可以被准确地标识和跟踪。

  2. Read View创建时机:当事务执行其第一个读操作时(或在某些情况下,即使是写操作),它会创建一个Read View。此Read View包括在该时点正在执行的所有其他活动事务的事务ID。

  3. 并发控制:数据库系统通常能够处理数千甚至数万的并发事务。事务调度、锁管理和并发控制策略都是为了确保系统的平稳运行而设计的。

  4. 资源限制:如果系统资源受到限制,如内存不足,可能会影响事务的处理。但即使在这种情况下,事务也不会“被遗忘”。可能的结果是,事务可能会因资源不足而被延迟或失败。

  5. 死锁和等待:在高并发环境下,事务之间可能会因为竞争同一资源而进入死锁或等待状态。数据库引擎通常有死锁检测机制来处理这些情况,例如,通过中止其中一个事务来解决死锁。

简单来说,数据库系统如InnoDB,被设计为在高并发环境下可靠地工作,确保每个事务都被正确地跟踪和管理,不会出现事务“丢失”或“被遗忘”的情况。


作者继续刨根问底

上面所说的:可能的结果是,事务可能会因资源不足而被延迟或失败。这个会不会导致新事务才开始活动,但是需要刚刚回收的数据呢?

如果一个事务因为资源限制(如内存不足)而被延迟,它的数据视图(Read View)仍然会基于它启动时的状态来保证数据的一致性。在MVCC中,即使这个事务实际的操作被延迟了,系统仍然会记住这个事务开始时的状态,并保留它需要的所有数据版本。所以,从这个角度来说,被延迟的事务不会因为版本清理而看不到它需要的数据。

进一步说明:

  • 数据的版本保留:如果一个事务已经开始,InnoDB存储引擎保证这个事务可以看到所有在它开始时就存在的数据版本。这些版本不会被清除,即使这些数据版本的生命周期超过了通常的垃圾收集周期。也就是说,事务可以被延迟或失败,可能会被重新启动。但它在之前一定开始过,一定会看到Read View的风景,即使失败、即使挂起、即使重启,它也会守护住心中的数据版本不被回收。

  • 垃圾收集的条件:数据版本只有在确定没有任何活动的事务需要它时才会被清理。这意味着,即使有事务被延迟,它仍然算作一个“活动事务”,数据库系统不会回收它可能需要的数据版本。

在实践中,即使事务延迟或失败,InnoDB的MVCC机制也确保数据的一致性不会受到影响。它通过延迟版本清理直到确定没有事务再需要旧版本来实现这一点。如果一个事务在被延迟或失败后重新启动,它将需要依赖当前有效的数据版本,而不是已经被清理的旧版本。


MVCC——一劳永逸?

MVCC(多版本并发控制)确实是为了解决数据库系统中的并发问题而设计的,如幻读(Phantom Reads)、不可重复读(Non-repeatable Reads)、脏读(Dirty Reads)等。这些问题通常是由于多个事务同时操作相同的数据集并试图提交更改时发生的。MVCC通过为每个读取事务创建数据的一个快照来避免这些问题,从而确保事务在其执行期间看到的数据保持一致,即使其他事务正在并发修改数据。

尽管MVCC提供了并发控制的机制,但仍然可以设置事务的隔离级别,以模拟和观察不同的并发问题:

  1. 脏读:一个事务读取另一个未提交事务的数据,结果那个未提交的事务出现意外撤回了,但是数据还是被记住了。在MVCC模型中,事务只能看到已提交的更改,因此默认情况下不会出现脏读。但在某些数据库中,你可以将事务隔离级别设置为Read Uncommitted来模拟脏读。

  2. 不可重复读:在同一个事务中,一个操作读取了两次数据,两次读取之间另一个事务进行了提交,导致第一次和第二次读取的数据不一致。在MVCC中,可以通过设置较低的隔离级别(如Read Committed)来观察不可重复读。

  3. 幻读:指一个事务内读取到了另一个事务新增的数据行。即使在MVCC中,如果事务隔离级别不是Serializable,也可能会遇到幻读问题。在Repeatable Read隔离级别下,幻读问题可以被控制。

  4. 可串行化(Serializable):这是最高的隔离级别,它通过加锁或其他机制,确保事务的执行结果与串行执行的结果一致。这个级别通常可以防止上述所有并发问题,但可能会导致性能下降和锁竞争。

即使使用了MVCC,如果隔离级别设置得不够高,还是有可能会遇到这些问题。不同的数据库和存储引擎可能会对这些隔离级别的具体实现有不同的策略。因此,了解并设置合适的事务隔离级别仍然是DBA和数据库开发者需要考虑的重要部分。

你可能感兴趣的:(数据库,mysql,数据库)