MySQL的事务、MVCC相关实现

文章详细介绍了MVCC(多版本并发控制)的概念及其相对于锁的并发控制的优势,特别是在高并发读取场景下的性能提升。接着,讨论了MySQL的事务特性,包括原子性、一致性、隔离性和持久性,并重点阐述了隔离级别的不同及幻读问题。此外,提到了MySQLInnoDB存储引擎的默认隔离级别和MVCC的实现方式,以及RedoLog和UndoLog在数据恢复和事务回滚中的作用。最后,通过实例展示了MVCC如何解决幻读问题,确保事务读取数据的一致性。

1、MVCC定义

MVCC(Multi Version Concurrency Control的简称),代表多版本并发控制。与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的优势:读不加锁,读写不冲突。在读多写少应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。

2、mysql架构

在这里插入图片描述

3、mysql事务隔离机制

3.1、事务的特征

	原子性:要执行的事务是一个独立的操作单元,要么全部执行,要么全部不执行

	一致性:事务的一致性是指事务的执行不能破坏数据库的一致性,一致性也称为完整性。一个事务在执行后,数据库必须从一个一致性状态转变为另一个一致性状态。

	隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行,SQL92规范中对隔离性定义了不同的隔离级别:读未提交(READ UNCOMMITED)->读已提交(READ COMMITTED)->可重复读(REPEATABLE READ)->序列化(SERIALIZABLE)。隔离级别依次增强,但是导致的问题是并发能力的减弱。

	持久性:当我们执行一条 insert 语句后,数据库必须要保证有一条数据永久地存放在磁盘中。

3.2、隔离性

隔离级别

隔离级别脏读不可重复读幻读概念
READ UNCOMMITED(读未提交)事务能够看到其他事务没有提交的修改,当另一个事务又回滚了修改后的情况,又被称为脏读dirty read
READ COMMITTED(读已提交)×事务能够看到其他事务提交后的修改,这时会出现一个事务内两次读取数据可能因为其他事务提交的修改导致不一致的情况,称为不可重复读
REPEATABLE READ(可重复度)×事务在两次读取时读取到的数据的状态是一致的
SERIALIZABLE(系列化)×××可重复读中可能出现第二次读读到第一次没有读到的数据,也就是被其他事务插入的数据,这种情况称为幻读phantom read, 该级别中不能出现幻读

大多数数据库系统的默认隔离级别都是READ COMMITTED(但MySQL不是),InnoDB存储引擎默认隔离级别REPEATABLE READ,通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

3.3、mysql日志

MySQL Innodb中跟数据持久性、一致性有关的日志,有以下几种:

Bin Log:是mysql服务层产生的日志,常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的

Redo Log:记录了数据操作在物理层面的修改,mysql中使用了大量缓存,修改操作时会直接修改内存,而不是立刻修改磁盘,事务进行中时会不断的产生redo log,在事务提交时进行一次flush操作,保存到磁盘中。当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。

Undo Log: 除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它记录了修改的反向操作,比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。

3.4、mvcc实现

MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。

SELECT

InnoDB会根据以下两个条件检查每行记录:

只有符合上述两个条件的记录,才能返回作为查询结果

InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。

行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

INSERT

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE

InnoDB为删除的每一行保存当前系统版本号作为行删除标识。

UPDATE

InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作

举例

transaction 1:

start transaction;
insert into demo values(NULL,'zhangsan');
insert into demo values(NULL,'lisi');
commit;

假设系统初始事务ID为1;数据为下

idname创建版本号删除版本号
1张三1undefined
2lisi1undefined

transaction 2:

start transaction;
select * from demo; //(1)
select * from demo; //(2)
commit

假设当执行事务2的过程中,准备执行语句(2)时,开始执行事务3:

transaction 3:

start transaction;
insert into demo values(NULL,'wangwu');
commit;
idname创建版本号删除版本号
1zhangsan1undefined
2lisi1undefined
3wangwu3undefined

事务3执行完毕,开始执行事务2 语句2,由于事务2只能查询创建版本号小于等于2的,所以事务3新增的记录在事务2中是查不出来的,这就通过乐观锁的方式避免了幻读的产生。

假设当执行事务2的过程中,准备执行语句(2)时,开始执行事务4:

transaction 4:

start transaction;
update mvcctest set name = '李四' where id = 2;
commit;

InnoDB执行UPDATE,实际上是新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要UPDATE的行的删除版本号

idname创建版本号删除版本号
1zhangsan1undefined
2lisi1undefined
3wangwu3undefined
4李四4undefined

事务4执行完毕,开始执行事务2 语句2,由于事务2只能查询创建版本号小于等于2的,所以事务修改的记录在事务2中是查不出来的,这样就保证了事务在两次读取时读取到的数据的状态是一致的

假设当执行事务2的过程中,准备执行语句(2)时,开始执行事务5:

transaction session 5:

start transaction;
delete from mvcctest where id = 2;
commit;
idname创建版本号删除版本号
1zhangsan1undefined
2lisi15
3wangwu3undefined
4李四4undefined

事务5执行完毕,开始执行事务2 语句2,由于事务2只能查询创建时间小于等于2、并且过期时间大于等于2,所以id=2的记录在事务2 语句2中,也是可以查出来的,这样就保证了事务在两次读取时读取到的数据的状态是一致的

4.总结
假设创建版本号为insertVersion,删除版本号为deleteVersion 当前版本号为currentVersion

MVCC查询机制就是靠下面的这个条件来实现的。只是默认在引擎中替我们做到了

select * from demo where
currentVersion<=insertVersion and currentVersion>=deleteVersion
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值