前言:
在当今互联网时代,数据库扮演着数据存储和管理的关键角色。对于大型Web应用程序和企业级系统而言,高效地处理并发访问和事务管理是至关重要的。多版本并发控制(MVCC)是一种数据库事务处理的技术,旨在提高并发性和数据一致性,而MySQL是其中一个广泛采用MVCC的数据库管理系统。
在本文中,我们将深入探讨MVCC的概念、原理和实现方式,特别关注MySQL中MVCC的实现。我们将探讨MVCC是如何克服传统数据库锁定机制的局限性,从而实现更高的并发性和更好的数据一致性。通过深入了解MVCC,读者将能够更好地理解MySQL的工作原理,并能够优化数据库设计和性能调优。
目录
前言:
当前读:
快照读:
MVCC:
实现原理:
RC隔离级别下:
RR隔离级别下:
总结:
本片的数据库表的属性,如果没有特殊说明,那默认就是(innoDB+RR)
在介绍MVCC之前,我们要先介绍两个概念:
当前读:
读取的是当前记录的最新版本,读取的时候还需要保证其他并发事务不能修改当前记录,会对当前记录加锁。对于我们日常的select......lock in share mode,select......for update,update,insert,delete都是一种当前读。
快照读:
读取的是数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。常见隔离级别下的select:
- RC:每一次select,都生成一个快照读。
- RR:开启一个事务之后,只有第一个select语句才会生成一张快照,此后读的都是快照中的数据,直到事务提交。
- Serializable:快照读退化为当前读。
我们用一个例子来看一下,以下为我们模拟的表数据(数据库默认使用InnoDB,隔离级别为RR):
我们同时开启两个MySQL客户端来对这张表进行操作:
先尝试读取数据,这种能读取到表中最新数据的模式就叫做当前读。现在我们来看看什么是快照读:
1.创建两个MySQL客户端,都开启事务,并且在第一个MySQL客户端中做一次SQL查询。
2.在第二个MySQL客户端中对id为1的用户姓名进行修改,并且提交当前事务。
3.重新在第一个MySQL客户端中查询。
我们会发现:第一个MySQL客户端中读取的数据竟然是老数据。这是为什么呢?
原因很简单:之前我们讲过MySQL的innoDB引擎在RR的隔离级别下,当我们开启事务的时候,只有第一次Select是当前读,读取完之后会生成一张快照,此后这个事务中后续的所有相同的select语句读的都是第一次Select所生成的快照。
当我们提交第一个MySQL客户端的时候之后,再尝试select * from emp ,会发生什么呢?
我们可以发现,此时就可以读取到正常的数据了。也就是说在InnoDB中select....... in share mode语句就是当前读。
MVCC:
MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种用于数据库管理系统中实现并发控制的技术。它允许多个事务同时对数据库进行读写操作而不会导致数据不一致或丢失。
MVCC 的核心思想是在数据库中维护多个数据版本,并根据事务的隔离级别来决定哪个版本对特定事务是可见的。快照读就为MVCC提供了一个非阻塞读的功能,MVCC的具体实现还需要依赖数据库中的三个隐式字段,undo log 日志,readView
实现原理:
1.记录中的隐藏字段:当我们尝试创建一张表之后,InnoDB会自动为我们加上两个字段:
- DB_TRX_ID:最近一次修改或删除记录的事务ID
- DB_ROLL_PTR:回滚指针,指向记录的上一个版本
- DB_ROW_ID:隐藏主键,如果表结构没有主键,将会生成该隐藏字段
2.undo log 日志:
- Undo 日志记录了对数据的修改操作,包括插入、更新、删除等。
- Undo 日志中记录了修改前的数据值,以及撤销操作所需的信息,以便在事务回滚或 MVCC 中使用。
- 当事务提交的时候,相关的Undo log 日志就被标记为可回收状态,可以在之后的操作中被回收
如果是insert语句,那么Undo log日志只需要在回滚的时候需要,当事务提交之后,会被立即删除。
如果是update,delete的时候,产生的undo log日志不仅在回滚的时候需要,在产生快照读的时候也需要,因此其不会被立即删除。(undo log 中残留的旧版本数据可供其他并发事务进行快照读)
3.undo log 版本链:
(本图来自黑马程序员,偷懒了没画,如有侵权,请联系我立即删除)
4.readview
readview是快照读SQL执行时候MVCC提取数据的依据,记录并且维护当前事务活跃的事务(未提交)的id。
ReadView包含了四个核心字段:
字段 | 含义 |
m_min_trx_id | 最小活跃事务ID |
max_try_id | 预分配事务ID,其实是当前最大事务ID+1(因为事务是自增的) |
m_ids | 当前活跃的事务ID集合 |
creator_trx_id | ReadView创建者的事务ID |
那么基于readView,其实就已经决定了哪些事务可以访问undo的哪些数据版本:
InnoDB不同的隔离级别,生成ReadView的实际不同:
- READ COMMITTED : 在事务每一次执行快照读的时候生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读的时候生成ReadView,后续一致复用该ReadView。
我们来口述以下在这两种不同的隔离级别下,具体读取的是哪一个版本的快照:
RC隔离级别下:
其实就是去做比较,将undo log 中的DB_TRX_ID套入比较规则中,只要一个比较符合规则,那么就可以进行读取该版本的数据。
经过一个一个的尝试,我们可以发现DB_TRX_ID=2的时候 ,符合比较规则2:trx_id<min_trx_id。
因此我们事务5中第一次查询id为30的记录的时候,实际上读的是DB_TRX_ID=2的快照读。
其实这四个比较看起来比较唬人,其实很好理解:RC是读已提交,也就是说我们的事务5在读取的时候,要么读取在自己之前已经提交了事务的版本数据,要么读取自己修改的数据。那我们返回表中看:只有事务2在表5第一条语句之前提交了事务,因此我们事务5可以读事务2的数据版本。
因此我们可以看到RC可以防止脏读,因为它用的数据版本就是一个已经提交了事务的数据版本,自然不可能读到其他事务还没有提交的数据。
RR隔离级别下:
RR隔离级别下,单个事务中一直复用的是该事务第一次查询版本快照。在当前案例中,事务5一直复用的就是自己第一次查询id等于30的数据记录。
因此我们可以看到RR可以防止重复读,这是因为事务一直使用的是自己第一次产生的readview,readview一样,那么匹配到的历史数据版本就一样,如果我们一直读取的都是一个历史版本数据,自然不可能出现两次读取结果不一致的情况了。
总结:
MVCC 是数据库系统中一种重要的并发控制机制,它通过在数据库中维护多个版本的数据来支持事务的并发执行,并提供了一致性读取和隔离性的保证。在 MVCC 中,每个事务都可以看到一个独立的数据版本,这使得读操作不会被写操作所阻塞,从而提高了数据库的并发性能。
MVCC 的实现主要依赖于两个关键组件:Undo 日志和版本链。Undo 日志记录了事务执行过程中对数据所做的修改操作,用于在事务回滚或撤销时恢复数据到之前的状态。而版本链则是指数据库中维护的多个数据版本之间的关系,包括当前版本、已提交版本和未提交版本等。
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!