MySQL的MVCC底层原理
Posted 我爱架构师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL的MVCC底层原理相关的知识,希望对你有一定的参考价值。
MVCC机制详解
MVCC主要是为了提高并发的读写性能,不用加锁就能让多个事务并发读写
一丶什么是MVCC?
MVCC (MultiVersion Concurrency Control) (注:与MVCC相对的,是基于锁的并发控 制,LockBased Concurrency Control)是一种基于多版本的并发控制协议,只有在InnoDB引 擎下存在。MVCC是为了实现事务的隔离性,通过版本号,避免同一数据在不同事务间的竞争,你 可以把它当成基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别未提交锁和已提交锁时 才会生效。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP 应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。具体见下面介绍。
二丶MVCC的实现机制
InnoDB在每行数据都增加两个隐藏字段,一个记录创建的版本号,一个记录删除的版本号。
在多版本并发控制中,为了保证数据操作在多线程过程中,保证事务隔离的机制,降低锁竞争 的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生 成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删 改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行 中,这样保证了每个事务操作的数据,都是互不影响的,也不存在锁的问题。
三丶MVCC下的CRUD
SELECT:
当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条 件:
1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事 务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这 行数据是存在的。
2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这 行数据没有被删除。
符合这两个条件的行可能会被当作查询结果而返回。
INSERT:
InnoDB为这个新行记录当前的系统版本号。
DELETE:
InnoDB将当前的系统版本号设置为这一行的删除ID。
UPDATE:
InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这 个版本号写到旧行的删除版本里。
这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简 单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每 一行存储更多的数据,做更多的检查工作,处理更多的善后操作。
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只 能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数 据。
版本链
对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐 藏列( row_id并不是必要的,我们创建的表中有主键或者非NULL唯一键时都不 会包含row_id列):
trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给 trx_id隐藏列。
roll_pointer:每次对某条记录进行改动时,这个隐藏列会存一个 指针,可以通过这个指针找到该记录修改前的信息
ReadView
对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了, 对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用 READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所 说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。ReadView中主要包含4个比较重要的内容:
1. m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
2. min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就 是m_ids中的最小值。
3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
4. creator_trx_id:表示生成该ReadView的事务的事务id
注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,
2, 3这三个事务,之
后id为3的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2,
min_trx_id的值就是1,
max_trx_id的值就是4。
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本 是否可见:
1)如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事 务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2)如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事 务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
3)如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事 务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
4)如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要 判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本 的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的 事务已经被提交,该版本可以被访问。
看文章之前,请先仔细阅读如下描述:
-------------------------------------------------重要内容---start-------------------------- --------------------
当执行查询sql时会生成一致性视图read-view,它由执行查询时所有未提交事务id数组(数组里 最
小的id为min_id)和已创建的最大事务id(max_id)组成,查询的数据结果需要跟read-view做 比对从而得到快照结果
版本链比对规则:
1. 如果落在绿色部分(trx_id < min_id),表示这个版本是已经提交的事务生成的,这个 数据是可见的;
2. 如果落在红色部分(trx_id > max_id),表示这个版本是由将来启动的事务生成的,是 肯定不可见的;
3. 如果落在黄色部分(min_id <= trx_id <= max_id),那就包括两种情况:(注意数 组中的含义:是在数组中不是范围看仔细了)
a.若row的trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见,当前自己的事 务是可见;
b.若row的trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见;
对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将 trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的 (delete_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对 应的记录,如果delete_flag标记为true,意味着记录已被删除,则不返回数据。
-------------------------------------------------重要内容---end--------------------------- -------------------
1. select1 第一次查询
-------------------------------------------------------------------------------------------- -------------------------
2. select1 第二次查询
-------------------------------------------------------------------------------------------- -------------------------
3. select1 第三次查询
-------------------------------------------------------------------------------------------- -------------------------
1. select2 会话查询:结果为:lilei2
-------------------------------------------------------------------------------------------- -------------------------
注意readview的生成
对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将 trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的 (delete_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对 应的记录,如果delete_flag标记为true,意味着记录已被删除,则不返回数据。
-----------------以下包括Excel的示例数据以及附件,以及processon的图形示例--------- ----------------
以上是关于MySQL的MVCC底层原理的主要内容,如果未能解决你的问题,请参考以下文章
ACID底层实现原理一致性非锁定读(MVCC的原理)BufferPool缓存机制重做日志刷盘策略隔离级别