Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL 事务

深入探究MySQL事务实现原理

作者:半亩方塘立身

数据库事务是指一组数据库操作,这些操作必须被视为一个不可分割的单元,要么全部执行成功,要么全部失败回滚,本文详细的给大家介绍了MySQL事务的实现原理,对我们学习MySQL有一定的帮助,感兴趣的同学可以跟着小编一起来探究

什么是数据库事务

数据库事务是指一组数据库操作,这些操作必须被视为一个不可分割的单元,要么全部执行成功,要么全部失败回滚。事务通常由多个SQL语句组成,这些语句可以读取、插入、更新或删除数据库中的数据。
事务具有ACID属性:

通过实现事务,数据库系统可以确保数据的完整性和一致性,以及并发访问时的正确性。如果一个事务中的任何一个操作失败,整个事务将被回滚到最初的状态,这确保了数据库的一致性。

Mysql如何保证原子性

undo log名为回滚日志,是实现原子性的关键。 InnoDB把这些为了回滚而记录的这些东西称之为undo log。这里需要注意的一点是,由于查询操作(SELECT)并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo log。undo log主要分为3种:

举个栗子:

sqlundo log
insertdelete
deleteinsert
update T set v=3 where v=1update T set v=1 where v=3

Mysql如何保证持久性

我们了解到InnoDB 为了提升读写效率,引入了Buffer Pool(缓存池):

如果MySQL宕机,缓存池中更新的数据还没有刷回到磁盘中,就会导致数据丢失。于是,redo log被引入进来解决这个问题。

redoundo在一次事务操作中是如何交互的?假设有A、B两个数据,值分别为1、2,开启事务分别对其进行修改A → 3,B → 4,在提交,过程如下:

事务redo&undo logo
begin;开启事务
记录A->3到redo log buffer
update T set A=3 where A=1;A修改为3
记录A=1到undo log
记录B->4到redo log buffer
update T set B=4 where B=2;B修改为4
记录B=2到undo log
记录A->3到redo log记录B->4到redo log
commit;事务提交

MySQL怎么保证隔离性

事务在并发情形下会互相干扰到的操作大体可以分为两类,与之相对应地,MySQL采用了两种方式来实现它们的隔离:

加锁:读取数据之前,对其加锁,阻止其他事务对数据进行修改

MVCC:不加任何锁,采用多版本并发控制实现,把数据库的行锁和行的多个版本结合起来,可以实现非锁定读,从而提高数据库的并发性能。

事务隔离级别

当数据库上有多个事务同时执行的时候,会带来以下问题:

问题描述举例
脏读一个事务读到了另一个事务未提交修改的数据。事务A开始一个更新操作,但是还没有提交,这时事务B读取了这个未提交的数据,就会产生脏读。
幻读一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据。事务A进行一个范围查询,此时事务B插入了一些符合该范围查询条件的新数据,当事务A再次进行相同的范围查询时,会发现多了一些之前没有的行,就产生了幻读。
不可重复读在一个事务中,多次查询的数据不一致。事务A读取了一行数据,然后事务B对这一行数据进行了更新,并且提交了,当事务A再次读取这一行数据时,会发现数据已经发生了变化,就产生了不可重复读。

为了避免这些问题的出现,数据库引入了隔离级别的概念,通过对不同隔离级别的设置,可以控制事务之间的隔离程度,从而避免并发问题的产生。不同的隔离级别有不同的特点和使用场景,需要根据实际情况进行选择。

以下是四个标准的事务隔离级别:

隔离级别含义脏读不可重复读幻读
读未提交,Read Uncommitted事务中的修改,即使没有提交,对其他事务都是可见的YYY
读已提交,Read Committed事务从开始到提交之前,所做的修改对其他事务都不可见NYY
可重复读,Repeatable read同一事务中多次读取同样的记录结果是一致的NNY
可序列化,Serializable在读取的每一行数据上加锁,强制事务串行执行NNN

脏读的解决

Innodb是通过在每行数据中增加一个隐藏的事务ID来实现mvcc,当一个事物开始时他会获取一个唯一的事务ID,该事务ID用来标记事务做的修改。当事务读取一行数据时,innodb会检查该行数据事务ID是否小于当前事务ID,如果是说明该行数据是未提交的数据,innodb会阻止该事务读取该行数据,从而避免了脏读的问题。

不可重复读的解决

innodb通过mvcc解决不可重复读的问题,在RR数据库隔离级别下,当我们使用快照进行数据读取的时候,只会在第一次读取的时候生成一个ReadView,后续所有快照读都是使用同一个快照,所以就不会发生不可重复读的问题了。

可重复读模式下举个栗子: 事务隔离级别为RR:

创建个测试表,并插入一条数据(1,1,1)

create table table1(
    id int(11) not null,
    a varchar(50) default null,
    b varchar(50) default null,
    primary key(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

分别开启两个事务测试:

幻读的解决

innodb的mvcc和间隙锁在一定程度上避免了幻读的发生,但是没有办法完全避免,当一个事务读的时候会导致幻读的发生。

幻读的case

create table user(
    id int not null,
    name varchar(50),
    age int,
    primary key(id)
);
insert into user values(1,'张三',10),(2,'李四',20),(3,'王二',30);

MVCC实现

每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。

MySQL中每条记录,除了我们自定义的字段之外,还有数据库隐藏定义的三个字段:

字段描述
DB_TRX_ID6字节,最近修改事务id,记录创建这套记录后者最后一次修改该记录的事务id
DB_ROLL_PTR7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog
DB_ROW_ID6字节,隐藏的主键,如果数据表没有主键,那么innodb会生成一个6字节的row_id

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

InnoDB 并不会真正地去开辟空间存储多个版本的行记录,只是借助 undo log 记录每次写操作的反向操作。所以B+ 索引树上对应的记录只会有一个最新版本,InnoDB 可以根据 undo log 得到数据的历史版本,从而实现多版本控制。

Read View

什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)

所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,即可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候。

RC、RR级别下的InnoDB快照读有什么不同?

在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;

在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

以上就是深入探究MySQL事务实现原理的详细内容,更多关于MySQL 事务的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文