赞
踩
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败,它具有以下四个基本要素。
ACID:原子性(Atomicity)、
一致性(Correspondence)、
隔离性(Isolation)、
持久性(Durability)
1、原子性:事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。如果事务在执行过程中发生错误,事务会被回滚(Rollback)到事务开始前的状态;
2、一致性:事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不可能A被扣了钱,B却没收到转账。要保证最后现实逻辑与数据库操作后的数据一致
3、隔离性:事务的隔离性意味着并发的事务之间是相互隔离的。即一个事务的内部操作及正在操作的数据必须封锁起来,不被企图进行修改的其他事务看到。
4、持久性:事务完成后,事务对数据库的所有更新将被保存起来,不会回滚。
一致性是事务的最终目的,原子性、隔离性、持久性都是为了实现一致性!!事务的执行流程如下图所示:下文会具体解释相关日志和该执行流程
在了解原子性实现原理之前,我们先要了解一下MySQL的WAL技术,WAL全称为Write-Ahead Logging,预写日志系统。其主要是指MySQL在执行写操作的时候并不是立刻更新到磁盘上,而是先记录在日志中,并更新内存,之后在系统空闲的时候将其更新到磁盘中。日志主要分为undo log、redo log、binlo.
redo log和undo log是InnoDB引擎的,binlog是server层的
undo log和binlog是逻辑日志,redo log是物理日志
redo log和binlog区别
- redo log是属于innoDB层面,binlog属于MySQL Server层面的
- redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
- redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖
- 应用场景不同:binlog可以作为恢复数据使用,主从复制搭建;redo log作为异常宕机或者介质故障后的数据恢复使用
原子性的实现主要依靠的是Undo log日志。原子性的体现主要是在sql在执行过程中发生错误而发生回滚上。回滚是要回到执行前的一个状态,那么怎么回到执行前的状态呢?我们是不是就得将执行前的状态记录下来。Undo log 就是实现这个功能的一个日志。
这个回滚日志Undo log,记录的东西非常简单:
Redo log用来记录某数据块被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据;Undo log是用来记录数据更新前的值,保证数据更新失败能够回滚。
我们需要先来了解下InnoDB是怎么来读写数据的。我们知道数据库的数据都是存放在磁盘中的,但是磁盘I/O的成本是很大的,如果每次读写数据都要访问磁盘,数据库的效率就会非常低。为了解决这个问题,InnoDB提供了 Buffer Pool 作为访问数据库数据的缓冲。
Buffer Pool 是位于内存的,包含了磁盘中部分数据页的映射。当需要读取数据时,InnoDB会首先尝试从Buffer Pool中读取,读取不到的话就会从磁盘读取后放入Buffer Pool;当写入数据时,会先写入Buffer Pool的页面,并把这样的页面标记为dirty,并放到专门的flush list上,这些修改的数据页会在后续某个时刻被刷新到磁盘中(这一过程称为刷脏,由其他后台线程负责) 。
通过前面的介绍,我们知道InnoDB使用 Buffer Pool 来提高读写的性能。但是 Buffer Pool 是在内存的,是易失性的,如果一个事务提交了事务后,MySQL突然宕机,且此时Buffer Pool中修改的数据还没有刷新到磁盘中的话,就会导致数据的丢失,事务的持久性就无法保证。为了解决这个问题,InnoDB引入了 redo log来实现数据修改的持久化。根据我们在上面所介绍的WAL机制,先写日志,再写磁盘,有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个 能力称为crash-safe。
redo log是由两部分组成的:一是内存中的重做日志缓冲(redo log buffer);二是用来持久化的重做日志文件(redo log file)。我们的数据最开始是在内存之中的,当我们提交事务的时候,redo log会有三种提交方式,来把内存的数据写到磁盘当中,这三种方式可以设置的
事务提交的时候,写入redo log 相比于直接刷脏的好处主要有三点:
后我们来看面试被问的最多的隔离性。当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non- repeatable read)、幻读(phantomread)的问题,为了解决这些问题,就有了“隔离级别”的概念。
不可重复读和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于删除或新增!!
下面我们来看为了解决这些问题出现的隔离级别。首先要知道,隔离得越严实,效率就会越低。因此很多时候,我们都要 在二者之间寻找一个平衡点。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、 读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。 具体解释如下:
数据库的隔离性就是通过加锁和MVCC来实现的。从上面可以看到,可重复读的隔离级别会出现幻读的问题,而MySQL的默认隔离级别是可重复读,并且解决了幻读的问题。简单来说,MySQL的默认隔离级别解决了脏读、幻读、不可重复读的问题。我们先来看数据库并发场景有哪些
数据库并发场景有三种,分别为:
写-写操作的线程安全是通过加锁来实现的,具体可以看我之前总结的一篇文章:MySQL锁总结(全面简洁 + 图文详解)_李孛欢的博客-CSDN博客 。但是加锁的操作会严重影响数据库的性能和并发量,因此出现了MVCC---多版本并发控制。MVCC是一种用来解决读-写冲突的无锁并发控制,MVCC在数据库中的实现,就是为了解决读(快照读)写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。MVCC可以为数据库解决以下问题:
下面我们来看看MVCC的具体实现原理,参考::阿里P7要求这么低吗?老哥给你讲清楚什么是MySQL的MVCC_哔哩哔哩_bilibili
像select lock in share mode(共享锁),select for update;update,insert,delete(排他锁)这些操作都是一种当前读。就是它读取的是记录的最新版本,读取时要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本。
如上所示,undo log和回滚指针将最新记录和历史记录连接起来形成了一个版本链。那么我读取数据的时候到底选择哪个版本呢? 这就要用read view来决定了。
read view 由下面这四部分组成。所谓活跃的事务ID就是还没有commit的事务id。那么readview是如何判断的呢?如下图所示:
这里的trx_id是每一个版本的事务id,如前面的图所示,张三的trx_id为1,李四的为2。
了解了MVCC的原理,我们来看看RC和RR是怎么通过MVCC实现的
正是Read View生成的时机不同,从而造成RC、RR级别下的快照读的结果不同。
总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View,这样也解决了快照读的幻读问题。当前读的幻读问题是通过间隙锁解决的。
————————————————
版权声明:本文为CSDN博主「李孛欢」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_61543601/article/details/125083372
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。