MVCC

Multiversion Concurrency Control,象征多版本并发管制,也就是MySql InnoDB引擎解决幻读问题的计划

MySql InnoDB默认隔离级别是可反复读,本文后续会围绕MVCC开展,简述MVCC解决该隔离级别问题的原理&形式

相干知识点链接

数据库隔离级别 -> 内容如题
MySql 共享锁 & 排他锁 -> 讲述SQL层面会用到的2种锁
MySql 行级锁 & 表级锁 -> 讲述不同存储引擎的锁类型

快照读

快照读又称为一致性读,指读取的是快照数据,MySql InnoDB 中不加锁的 SELECT 都属于快照读,即非阻塞读

SELECT * FROM table_name WHERE

上文知识点中会提到,倒退出快照读是为了进步并发性能,快照读的实现是基于MVCC,防止了加锁操作从而升高了开销;在多版本控制中,快照读并不一定是最新版本的数据,可能是历史版本;快照读的前提是隔离级别可反复读,串行化级别下会进化成以后读

以后读

以后读获取的是最新数据,读取时要保障其余并发事务不能批改以后记录,会对读取的记录进行加锁,加锁语句可在上述链接 共享锁 & 排他锁 中查看

以后读带来的问题

事务A

select * from table where column > 1 for update

事务B

insert into table(column) values (2);insert into table(column) values (3);

事务A在事务B之前应用以后读获取数据,事务B在事务A之后进行数据写入,在主库中执行此命令可能不会有问题,然而如果事务B的数据在事务A完结之前写入结束,从库通过binlog同步,则在从库中事务A产生的改变可能对事务B的数据失效,导致主库从库数据不统一;

间隙锁

对于上述问题,MySql 引入了间隙锁来解决,其原理为,在事务A加锁是时,MySql会对其搜寻的数据的间隙加上锁,即对所有column > 1的数据加锁,即便数据不存在,或者说,不容许任何事务对
column > 1进行写入,此时就能阻止事务B的执行,从而保障了主库从库数据统一

间隙锁带来的问题

在上述案例中,如果事务A迟迟不执行结束,会阻塞所有写入column > 1数据的事务,此时并发效率是非常低的,如果锁住的数据量过大,都会导致一系列问题,在高并发的环境下是无奈承受的,因为在MySql InnoDB中,默认是敞开间隙锁的,应用须要独自开启

innodb_locks_unsafe_for_binlog

快照读解决的问题

承接上述,在以后读带来的一些列问题,为了提高效率,MySql 引入了快照的概念;简述而言,在事务开始时,MySql 会给影响到的数据加一个版本号(trx_id),这个版本号即为以后事务id,当后续有其余事务对数据进行操作,版本号会递增,在后面的事务查问时,只会匹配到版本号等于或小于以后事务id的数据,因而无奈读取到后续事务写入的数据,从而解决幻读,与不可反复读的问题;快照读的实现,次要依赖于版本链 & Undo & Read View

版本链

InnoDb会给数据退出三个暗藏列

// 事务标识,也能够了解为版本号1.trx_id// 回滚指针,指向这条记录的上一个版本2.roll_pointer// 当表不存在主键时,默认退出,用来标记数据是否删除,与本文关联不大,后续不再讲述3.row_id

由trx_id 来标记数据的版本,即上一次事务的版本号;每次对数据进行改变,都会记录一条undo日志,每条undo日志都有一个roll_pointer属性(Insert操作对应的undo日志没有该属性,因为该数据并没有更早的版本);随着更新次数的增多,所有的版本都会被roll_pointer连接成一个链表,这个链表称为版本链,版本链的头节点就是以后记录最新的值;另外,每个版本中蕴含生成该版本对应的事务id(trx_id,即以后数据的版本号),在事务中判断版本可见性的时应用

Undo log

用于记录数据被批改之前的日志(即数据历史版本),在批改之前会先把数据写入undo log中,当事务进行回滚时能够通过undo log中的日志进行数据还原;undo log保障事务进行rollback时的原子性和一致性,事务进行回滚的时候应用undo log的数据进行复原;在MVCC中,通过读取undo log的版本号(trx_id)实现不同事务读取本人可见的数据;undo log 次要分为两种

// 事务在insert时产生的undo log,只在事务回滚时须要,在事务提交后即可抛弃1.insert undo log// 事务在update & delete时产生的undo log,在事务回滚 & 快照读时须要2.update undo log(次要)

事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统以后的一个快照。

记录并保护零碎以后沉闷事务的ID(trx_id)(没有commit,当每个事务开启时,都会被调配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是零碎中以后不应该被本事务看到的其余事务id列表。

Read View

当事务执行快照读的时候,对该记录创立Read View(读视图),用它作为条件判断以后事务能看到的数据版本;可能是以后最新的数据,也有可能是该行记录的undo log外面的某个版本的数据;即trx_id小于或等于以后事务id的数据

Read View的属性

// 以后零碎沉闷事务id汇合(即未提交的事务)1.trx_ids// 创立以后Read View时,以后零碎最新事务id+1,即新事务版本号比之前的大2.low_limit_id// 创立以后Read View时,未提交的事务最小的id3.up_limit_id// 创立以后Read View的事务id4.creator_trx_id

可见判断

1.Read View中,数据版本号(trx_id)小于以后零碎未提交事务的最小id,则阐明该数据在以后所有未提交事务之前就已生成,故可见
2.Read View中,数据版本号(trx_id)等于以后事务id,则阐明该数据由以后数据创立,故可见
3.Read View中,数据版本号(trx_id)小于以后事务id,且该数据版本号(trx_id)不存在于沉闷事务id中(trx_ids),则阐明操作该数据的事务已提交,故可见
4.Read View中,数据版本号(trx_id)小于以后事务id,且该数据版本号(trx_id)存在于沉闷事务id中(trx_ids),则阐明操作该数据的事务未提交,故不可见
5.Read View中,数据版本号(trx_id)大于以后事务id,则阐明该数据是以后事务之后的事务创立的,故不可见

参考资料:
https://blog.csdn.net/SeekN/a...
https://baijiahao.baidu.com/s...
https://baike.baidu.com/item/...
https://blog.csdn.net/zuodaoy...