jsp网站开发的优点,app制作过程和网站一样吗,东莞58同城广告推广公司,做mla网站文章目录 0.前言1.基础介绍1.1. 什么是MVCC?1.1. 什么是当前读和快照读#xff1f;1.1. 当前读#xff0c;快照读和MVCC的关系1.1. MVCC能解决什么问题#xff0c;好处是#xff1f;1.1.1. 提高并发性能1.1.2. 避免死锁1.1.3. 解决脏读、不可重复读和幻读等问题1.1.4. 实现… 文章目录 0.前言1.基础介绍1.1. 什么是MVCC?1.1. 什么是当前读和快照读1.1. 当前读快照读和MVCC的关系1.1. MVCC能解决什么问题好处是1.1.1. 提高并发性能1.1.2. 避免死锁1.1.3. 解决脏读、不可重复读和幻读等问题1.1.4. 实现非阻塞读1.1.5. 提供一致性视图 2. MVCC的实现原理2.1. 隐式字段2.1. undo日志场景示例 2.1. Read View(读视图)基本介绍场景示例原理readview如何判断版本链中的哪个版本可用呢重点 2.1. 整体流程 3. MVCC相关问题3.1. RR是如何在RC级的基础上解决不可重复读的3.1.RC,RR级别下的InnoDB快照读有什么不同 参考资料 0.前言 在数据库领域对数据进行并发操作是常见的需求。为了保证数据的一致性和事务的隔离性不同的数据库系统采用了不同的并发控制技术。其中多版本并发控制MVCCMultiversion Concurrency Control是MySQL中InnoDB存储引擎采用的一种非常重要的并发控制技术。
MVCC通过创建数据在某个时间点的快照让事务能够访问到一个一致的数据视图而不会被其他事务操作影响。这样不仅可以提高并发性能而且可以避免在读取数据时进行加锁大大增强了数据库的并发处理能力。
在本篇博客中我们深入探讨MySQL的MVCC实现机制包括Undo日志、Read View以及事务链等概念。我们将详解MVCC如何在保证数据一致性的同时提高数据库的并发处理能力。 理解和掌握MySQL中MVCC的工作原理以更好地进行数据库设计和优化。 虽然我们的标题是 MySQL的MVCC实现机制详解大家不要误认为MVCC机制是MySQL独有的。 MVCC 不是MySQL的特有机制除了MySQL 使用了MVCC机制其他数据库版本也使用了 以下是一些采用了多版本并发控制MVCC策略的数据库 PostgreSQL它使用 MVCC 提供多个并发用户间的一致性视图。 MySQL在可重复读取隔离级别下MySQL的InnoDB存储引擎利用 MVCC 解决读写冲突提供快照数据而非最新数据。 Oracle尽管Oracle使用了MVCC但其实现方法与PostgreSQL和MySQL的InnoDB不同。在Oracle中读操作不会阻塞写操作反之亦然。 SQLiteSQLite使用了snapshot isolation它的核心概念与MVCC相似都是在事务开始时提供一个快照而非实时数据。 CouchDB 和 MongoDB这两个NoSQL数据库也采用了MVCC或类似技术。 Apache HBase作为开源的非关系型分布式数据库HBase是Google BigTable的Java实现也使用了MVCC。 Apache Cassandra这是Facebook开发的一款开源分布式NoSQL数据库系统用于满足高速读写需求如Inbox搜索它也实现了MVCC。 MariaDB作为MySQL的一个开源分支版本MariaDB的InnoDB存储引擎也使用了MVCC。 Microsoft SQL Server在读已提交快照和快照隔离级别下SQL Server使用了MVCC。 1.基础介绍
1.1. 什么是MVCC?
MVCC全称是多版本并发控制(Multi-Version Concurrency Control)是一种用来解决数据库并发问题的方法。在高并发的场景下为了提高性能采用MVCC是一种比单纯的加锁更有效的方式。
MVCC的工作原理是每次对数据进行更新操作都不会直接覆盖原有数据而是为数据添加一个新的版本。并且每个事务都可以看到一个一致的快照版本这个版本在该事务启动时就已经确定。
这样就可以实现在不阻塞读操作的前提下进行写操作也就是实现了读写分离提高了数据库的并发处理能力。
MVCC实现机制主要包括以下几个方面 数据版本化每一条数据都有一个版本号每次修改数据都会生成一个新的版本。查询时只要在版本列表中找到对应的版本即可而不需要等待数据修改完成。 读视图每一次事务开始时都会生成一个读视图这个读视图记录了开始该事务时正在执行的所有其他事务的事务ID。在进行查询操作时只查询在读视图记录的事务ID之前开始的事务对应的数据版本。 undo日志在修改数据时会把修改前的数据版本写入undo日志。如果有其他事务需要访问修改前的数据版本可以直接从undo日志中获取。 MVCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。在这两个隔离级别下读操作不会产生任何锁大大提高了查询性能降低了锁冲突的概率从而使得更多的应用可以并发运行。
1.1. 什么是当前读和快照读
在数据库中读操作主要分为两种当前读Current Read和快照读Snapshot Read。 当前读就是读取记录的最新版本也就是最新的数据。如果数据在读取过程中被其他事务修改了那么会读取到最新的数据内容。当前读会对读取的数据加锁阻止其他事务对数据进行修改。当前读主要出现在UPDATE、DELETE、INSERT、SELECT…FOR UPDATE这些需要进行写操作的SQL语句中。 快照读读取的是记录在事务开始时的版本也就是读取快照中的数据。即使在读取过程中数据被其他事务修改读取到的数据内容也不会改变。快照读不会对读取的数据加锁不会阻止其他事务对数据进行修改。快照读主要出现在普通的SELECT语句中。
这两种读操作的主要区别在于是否对读取的数据加锁以及读取的是数据的哪个版本。
1.1. 当前读快照读和MVCC的关系
当前读和快照读都是MySQL中的读操作它们的区别在于读取的数据版本和是否加锁。然而这两种读操作并不能完全满足并发控制的需求。这就是MVCC(多版本并发控制)的作用。
MVCC是一种用于实现事务并发控制的机制。在MVCC中每次对数据的修改都会创建一个新的数据版本。不同的事务会看到不同版本的数据这取决于事务开始的时间以及事务的隔离级别。
当进行快照读非锁定读时MVCC允许事务读取一个旧的数据版本。这意味着在同一时间点不同的事务可以看到同一行数据的不同版本避免了因等待锁而导致的阻塞。这在幻读phantom read和非可重复读non-repeatable read这两种情况下特别有用。
当进行当前读锁定读时如UPDATE或SELECT FOR UPDATE会读取最新版本的数据并对其加锁以防止其他事务进行修改。
因此MVCC、当前读和快照读是密切相关的。MVCC通过提供一种机制使得当前读和快照读能够在并发事务中同时有效地工作从而提高数据库的整体性能。
1.1. MVCC能解决什么问题好处是
1.1.1. 提高并发性能
MVCC允许多个读操作与写操作同时进行无需等待锁因此可以大大提高并发性能。 在传统的数据库并发控制中为了保证数据的一致性通常会使用锁来阻止其他事务在当前事务完成之前读取或修改数据。这种方式虽然可以保证数据的一致性但是其并发性能较差因为读操作和写操作之间存在阻塞。 而在MVCC机制中对于读操作系统会创建一个数据版本的快照而不是直接对数据加锁这样即使有其他事务正在对数据进行修改当前事务也可以进行读操作。举例来说假设我们有一个在线购物系统当用户A查看某个商品的信息时即使此时商家正在修改这个商品的价格用户A也可以正常查看商品信息不会被阻塞。 对于写操作MVCC通过生成旧版本数据的拷贝来避免直接修改数据。这样当其他事务需要读取数据时即使数据已经被修改也可以通过读取这个旧版本数据的拷贝来获取数据而不需要等待写操作完成。比如上述在线购物系统中当商家修改商品价格时如果此时有用户正在查看这个商品那么用户看到的仍然是旧的价格无需等待商家修改价格的操作完成。 1.1.2. 避免死锁
由于读操作不需要加锁所以减少了产生死锁的可能。 在传统的锁定机制中如果两个或更多的事务在相互等待对方所持有的锁就会发生死锁。例如如果事务A锁定了资源1并试图获得资源2而事务B已经锁定了资源2并试图获得资源1那么就会出现死锁因为每个事务都在等待对方释放其需要的资源。 而在MVCC中由于读操作不需要加锁事务可以在不影响其他事务的情况下读取数据。这就意味着读操作不会因为等待其他事务释放锁而被阻塞从而减少了死锁的可能性。 例如假设有两个事务事务A和事务B它们都需要读取和修改同一条数据。在MVCC中事务A可以先创建一个数据的快照进行读操作此时即使事务B也开始修改这条数据事务A的读操作也不会被阻塞。而当事务A要进行写操作时只有当事务B的写操作已经完成并且事务已经提交事务A的写操作才会被阻塞。这样就大大降低了死锁的可能性。 1.1.3. 解决脏读、不可重复读和幻读等问题
通过在每个事务处理其自己的快照并在需要时创建对象的新版本MVCC能够解决脏读、不可重复读和幻读等事务隔离问题。 在MVCC(多版本并发控制)模型中每个事务处理的都是数据库在某个时间点的快照并且在多个事务处理同一个数据项时会生成该数据项的新版本。这种机制能有效解决一系列事务隔离的问题 脏读脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。在MVCC机制中每个事务都只读取事务开始时的数据快照不会读取到其他未提交事务的数据因此避免了脏读。 不可重复读不可重复读是指在一个事务内多次读同一数据时由于其他事务的提交导致多次读取的结果不一致。MVCC通过在每个事务开始时创建数据的快照并在事务运行过程中一直使用此快照确保了在同一事务中多次读取同一数据总是返回同一个结果避免了不可重复读。 幻读幻读是指在一个事务内部先后执行两个查询操作第二个查询出现了第一个查询不存在的记录或者之前的记录消失这种现象称为幻读。可重复读RR隔离级别下的InnoDB能通过MVCC来防止幻读事务会看到一个一致的快照这个快照在事务开始时创建并在事务运行期间不会更改从而避免了幻读。 1.1.4. 实现非阻塞读
在MVCC模型下读操作不会阻塞写操作写操作也不会阻塞读操作。
1.1.5. 提供一致性视图
使用MVCC每个事务都在其开始时获取一个快照然后在该快照上执行所有操作这样可以保证事务执行过程中看到的数据是一致的。
2. MVCC的实现原理
2.1. 隐式字段
InnoDB引擎在MVCC实现过程中会在每行数据后面添加额外的系统隐藏字段 根据官方文档中描述是三个字段 DB_ROW_ID DB_TRX_IDDB_ROLL_PTR。但是从有的博客文章中看到还有一个字段DELETED_BIT 。我在相关官方文档中没有证实。如果哪位同仁找到官方的相关描述记得一下我非常感谢 我在mysql5.7和8.0版本的官方文档和代码里都去求证未果。
5.7版本 mvcc 官方文档 https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html8.0版本 mvcc 官方文档 https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.htmlMySQL源码地址 https://github.com/mysql/mysql-server/tree/8.0
DB_ROW_ID: 6bytes隐含的自增ID隐藏主键。如果数据表没有主键InnoDB会自动以DB_ROW_ID产生一个聚簇索引。DB_TRX_ID: 6bytes最近修改修改/插入事务ID。记录创建这条记录或最后一次修改该记录的事务ID。DB_ROLL_PTR: 7bytes回滚指针。指向这条记录的上一个版本存储于Undo Log里。DELETED_BIT: 1byte【我暂未证实存在只是在一些博客帖子中看到有同学在解释】该字段标识该行是否被删除。记录被更新或删除并不意味真的删除而是删除标志位变更。
通过这种方式InnoDB存储引擎通过DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID等隐式字段结合Undo Log实现了MVCC的功能同时保证了事务的ACID性质原子性、一致性、隔离性、持久性。
2.1. undo日志
Undo日志在MySQL的InnoDB中是用于实现数据一致性、事务回滚以及多版本并发控制(MVCC)的重要组成部分。Undo日志主要由以下几部分组成 Undo日志记录每个Undo日志记录包含了一个数据版本的信息。当一个事务对数据库中的某个记录进行修改时InnoDB会在Undo日志中生成一个包含这个记录修改前信息的Undo日志记录。 Undo日志段(Undo Log Segment)Undo日志记录被分组存储在Undo日志段中。每个事务在开始时会在Undo段中创建一个新的Undo日志记录这个记录包含了事务开始时候的数据状态。 回滚段(Rollback Segment)回滚段是Undo日志段的容器每个回滚段可以包含多个Undo日志段。InnoDB中默认存在128个回滚段。 Undo表空间(Undo Tablespace)Undo表空间是存储Undo日志的物理空间可以存储多个回滚段。
在InnoDB中Undo日志的生命周期从事务开始到事务结束。如果一个事务在执行过程中失败或者被显式回滚InnoDB将使用Undo日志记录来恢复数据的原始状态确保数据的一致性。如果事务执行成功并提交相关的Undo日志记录将被标记为可回收并在之后的清理操作中被删除。
概念和理论相对比较苦涩我们以商城系统中的商品表为示例。
场景示例
比如有个商品表。 第一步有个事务插入商品表插入了一条新记录记录如下名称为萝卜, price为1隐式主键是1事务ID和回滚指针我们假设为NULL。 第二步现在来了一个事务1对该记录的name做出了修改改为黄瓜。 第三步又来了个事务2修改商品表的同一个记录将price修改为2.5 。
我们可以推测出 undo日志的存储过程 和表内容变化如下
商品表的初始状态如下
名称价格隐式主键事务ID回滚指针萝卜11NULLNULL
然后事务1对名称字段做出了修改。这时对应的undo日志记录如下
事务ID表名行ID操作类型旧值新值1商品表1更新萝卜黄瓜
商品表的状态变为
名称价格隐式主键事务ID回滚指针黄瓜111指向事务1的undo日志
接着事务2对价格字段做出了修改。对应的undo日志记录如下
事务ID表名行ID操作类型旧值新值2商品表1更新12.5
商品表的状态变为
名称价格隐式主键事务ID回滚指针黄瓜2.512指向事务2的undo日志
这个过程中InnoDB会为每一次的修改操作生成一条undo日志记录。如果之后需要回滚事务InnoDB就可以利用这些undo日志记录恢复数据的原始状态。
2.1. Read View(读视图)
基本介绍
Read View或读视图是用来实现一致性非锁定读即MVCC多版本并发控制中的读操作的一种机制。
当一个事务需要执行一致性非锁定读操作时InnoDB会为该事务生成一个读视图。这个读视图包含了当前正在活动的所有事务的事务ID这些事务在生成读视图的时间点之后如果有新的数据改动都不会被该读视图所看到。换句话说只有在生成读视图时已经提交的事务改动的数据才会被该读视图看到。
具体来说当一个事务开始执行SELECT操作时如果该事务是在READ COMMITTED隔离级别下那么InnoDB会在每个SELECT语句之前为该事务生成一个新的读视图。如果该事务是在REPEATABLE READ隔离级别下那么InnoDB只会在事务开始时生成一个读视图然后在整个事务期间都使用这个读视图。
场景示例
假设我们有一个简单的银行账户表包含账户ID和余额两个字段
账户ID余额15002100031500
现在假设我们有两个并发的事务事务A和事务B。
1. 事务A开始它想要查看账户1和账户2的余额所以它执行一个SELECT语句。由于事务A是在REPEATABLE READ隔离级别下执行的所以InnoDB为它生成一个读视图。这个读视图捕获了数据库在事务A开始时的状态即账户1的余额为500账户2的余额为1000。
2. 此时事务B开始并且它向账户1汇入了100元然后提交了事务。数据库的实际状态变为了账户1的余额为600账户2的余额为1000。
3. 事务A再次执行SELECT语句想要再次查看账户1和账户2的余额。但是由于事务A在执行SELECT时使用的是在它开始时生成的读视图所以它看到的账户1的余额仍然是500账户2的余额仍然是1000而不是数据库的实际状态。这就是读视图如何实现一致性非锁定读的。
在这个例子中尽管事务B在事务A执行过程中改变了数据库的状态但是由于读视图的存在事务A看到的数据仍然是一致的不会受到事务B的影响。这就是InnoDB如何通过使用读视图来实现MVCC的。
原理
在一个readview快照中主要包括以下这些字段 m_ids活跃的事务就是指还没有commit的事务。 max_trx_id例如m_ids中的事务id为123那么下一个应该分配的事务id就是4max_trx_id就是4。 creator_trx_id执行select读这个操作的事务的id。 readview如何判断版本链中的哪个版本可用呢重点
从上到下分别为1234依次进行解释
trx_id表示要读取的事务id
1如果要读取的事务id等于进行读操作的事务id说明是我读取我自己创建的记录那么为什么不可以呢。
2如果要读取的事务id小于最小的活跃事务id说明要读取的事务已经提交那么可以读取。
3max_trx_id表示生成readview时分配给下一个事务的id如果要读取的事务id大于max_trx_id说明该id已经不在该readview版本链中了故无法访问。
4m_ids中存储的是活跃事务的id如果要读取的事务id不在活跃列表那么就可以读取反之不行 通过这种方式InnoDB可以为每个事务提供一个它自己的一致性视图这样即使数据库中的数据在事务执行过程中发生了改变事务看到的数据也会保持一致不会受到其他事务的影响。从而实现了事务的一致性读也就是所谓的snapshot快照读这是实现MVCC的关键。 2.1. 整体流程
MySQL的MVCC多版本并发控制主要涉及以下几个步骤 事务启动当一个事务启动并执行第一个操作时系统会为该事务分配一个唯一的事务ID。 读操作在发生读操作时InnoDB会创建一个Read View读视图。这个读视图记录了启动时所有正在执行的事务ID该事务在执行过程中只能看到在读视图创建之前已经提交的事务所做的修改对于在读视图创建之后其他事务所做的修改该事务是无法看到的。 写操作当事务进行写操作时InnoDB不会直接覆盖旧的数据而是将旧数据复制一份保存到undo日志中并生成一个新的版本数据新的数据上会记录下创建该版本的事务ID。同时InnoDB还会在多版本链表中插入一个新的版本链表中的版本按照事务ID从大到小的顺序进行排序。 事务提交当事务提交时系统会将该事务的ID号从全局的活动事务列表中删除。 版本回收当系统判断某个版本的数据已经不再需要时即没有任何一个活动的事务需要访问这个版本的数据就会回收这个版本的数据以释放存储空间。 6.整体执行流程 如下
3. MVCC相关问题
3.1. RR是如何在RC级的基础上解决不可重复读的
RCRead Committed级别和RRRepeatable Read级别是两种常见的事务隔离级别。
在RC级别每次读取都会读取到该行最新的数据因此一个事务在不同时间执行相同的查询可能会得到不一样的结果这就是所谓的“不可重复读”。
为了解决RC级别下的“不可重复读”问题RR级别的事务在开始时会创建一个快照snapshot也就是一个数据的副本。在事务进行过程中即使其他事务修改了数据由于每次读取都是读取的这个快照因此在一个事务内多次读取同一数据得到的都是一样的结果。
这就是RR级别如何在RC级别的基础上解决不可重复读问题的。这是通过牺牲一定的并发性能增加了数据一致性。不过在很多场景下数据一致性比并发性能更加重要因此RR级别也被广泛使用。
3.1.RC,RR级别下的InnoDB快照读有什么不同
既然我们已经了解了RCRead Committed和RRRepeatable Read的差异那么这两个隔离级别下的InnoDB快照读又有什么不同呢 主要的区别在于快照读的创建时间。 在RC级别下每一次语句执行之前都会创建一个新的Read View快照而无论这个事务执行了多少语句只要它还在执行就会持续创建新的Read View。因此即使在同一事务中多次读取同一行数据可能会读取到不同版本的数据即出现“不可重复读”。 而在RR级别下只有在事务刚开始执行第一个语句时会创建一个Read View之后在这个事务中的所有操作都只会看到这个Read View所代表的数据版本。其他事务在这之后做的修改对当前事务来说是不可见的因此在同一事务中多次读取同一数据总是能够读到相同的结果即保证了“可重复读”。 RC和RR级别下InnoDB快照读的差异主要是由RC和RR的隔离级别特性决定的RR级别相比RC级别提供了更高的数据一致性但是在并发性能上可能会有所下降。
参考资料 官方文档MySQL官方网站提供了关于各种存储引擎的详细文档包括InnoDB和MyISAM等。https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html 书籍《高性能MySQL》是一本非常全面的关于MySQL性能优化、架构设计和内部机制的书籍其中包含了大量关于存储引擎的内容。 知乎大佬写的 https://zhuanlan.zhihu.com/p/447372441