MySQL事务的特点与MySQL事务实现原理

时间:2021-11-25

最近在学习mysql事务的时候,遇到了很多问题,遇到这些问题是因为对mysql事务的实现原理,实现机制很多都不了解,这篇文档先大概了解一下MySQL事务实现原理,慢慢在深入,如有不对的地方请海涵,都是自己通过查资料,看视频得出的一下观点。

在说事务的实现原理之前,可以看看事务的特点,提前了解事务,对后续的阅读提供一些思考。

事务的特点

在事务的四个特点中,一致性是事务根本追求,而在某些情况下对事务的一致性造成破坏;

事务的并发执行

事务故障或系统故障

数据库系统通过并发控制技术和日志恢复技术来避免这种情况的发生

并发控制技术保证了事务的隔离性,是数据库的一致性状态不会因为并发执行的操作被破坏。

日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据的修改不会因系统崩溃而失去,保证了事务的持久性。

原子性的实现:Undo Log

undo log是为了实现事务的原子性,在MySQL数据innodb存储引擎中,还用undo log来实现多版本并发控制(简称:MVCC)

在操作任务数据之前,首先将数据备份到一个地方(这个存储数据备份的地方叫做undo log),然后进行数据的修改,如果出现了错误或者用户执行了RollBack语句,系统可以利用Undo Log 中的备份将数据恢复到事务开始之前的状态

注意:undo log 是逻辑日志,可以理解为

当delete 一条记录时,undo log 中会记录一条对应的insert 记录

当insert一条记录时,undo log 中会记录一条对应的delete 记录

当update 一条记录时,它记录一条对应相反的update记录。

innodb默认从磁盘读取数据是以页为单位读取的,每个页的大小为16KB。

undo log 不会改数据,只是记录了一条sql语句。针对某一行记录。

redo log 是物理日志,是看这个页里面的数据有没有被修改

持久性实现原理:Redo Log

思考一个问题

持久性是将数据持久化到磁盘,那如果将修改的数据还没有持久化到磁盘,数据库发生故障了怎么办?是不是造成数据丢失?

在redo log 里提供了缓存,每次数据就该的时候先将缓存里的数据修改完成,然后在持久化到磁盘,为了解决持久化之前数据库发生故障,断电等情况造成数据丢失,innodb又提供了WAL,预写日志,通过预写日志保证。

怎么保证的呢?

和Undo log 相反,Redo log 记录是新数据的备份。在事务提交之前,只要将Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log 已经持久化,系统可以根据Redo Log 的内容,将所有数据恢复到最新状态

Redo Log

又有一个问题,毕竟redo log 在内存中,每次修改的页没有持久化mysql 宕机了怎么办?redo log-buffer 中数据丢失了怎么办?

其实可以不用考虑这种情况,因为mysql的设定中,当你要commit事务时,redo log 才会持久化磁盘,既然你没有commit,mysql宕机了,那让重启就行,毕竟没有提交事务,这一个套流程是不完整的,没有要提交的东西,因此mysql 也不需要恢复什么。

redo log buffer 写入磁盘时机

innndb_log_buffer_size

InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。

Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。

于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求

既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。

(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。

Redo Log刷盘时机

从上述可以看出,每当事务提交时,先将redo log 持久化进磁盘

如何控制将mysql ,让mysql 在commit 事务时率先将redo log 持久化呢?

在mysql中提供了设置刷盘的设置参数

innodb_flush_log_at_trx_commit

该参数有几个选项:0、1、2

想要保证ACID四大特性推荐设置为1:表示当你commit时,MySQL必须将rodolog-buffer中的数据刷新进磁盘中。确保只要commit是成功的,磁盘上就得有对应的rodolog日志。这也是最安全的情况。

设置为0:每秒写一次日志并将其刷新到磁盘。

设置为2:表示当你commit时,将redolog-buffer中的数据刷新进OS Cache中,然后依托于操作系统每秒刷新一次的机制将数据同步到磁盘中,也存在丢失的风险。

如下图:

Redo Log刷盘时机

每次写的时候先从内存读取数据,读取完数据之后,如果找不到才会去磁盘。

那么如果更新数据,是怎样更新的,执行过程是什么样的,见下图:

更新数据流程

更新数据流程

隔离性的实现

隔离性跟其他两个有所区别,原子性和持久性是为了要实现数据的可性靠,比如要做到宕机后的恢复,以及错误后的回滚。那么隔离性是要做到什么呢?隔离性是要管理多个并发读写请求的访问顺序。这种顺序包括串行或者是并行说明一点,写请求不仅仅是指insert操作,又包括update操作。级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。

如下图:不同的隔离级别,出现的数据安全也不一样。

不同的隔离级别,出现的数据安全也不一样

在以上内容中讲到了,原子性和持久性是为了实现数的可靠性保障,能将所有事务操作后产生的数据都能保存下来,即使回滚,也能恢复数据,不会丢失。

那么隔离性要做的是什么呢?隔离性是要管理多个并发读写请求的顺序访问,不管是并行还是串行。

隔离性的实现

所以隔离性的实现,需要根据mysql的隔离级别来支持

隔离性在实现的过程中,主要通过读写锁和MVCC的方式控制。、

1.读写锁:

每次读操作需要获取一个共享锁,每次写操作需要获取一个写锁,共享锁之间不会产生互斥,因为不涉及到数据的更改,数据是安全的,所以其他并发情况下线程都可以读,但是写锁与共享锁之间 ,写锁与写锁之间会产生互斥。当发生锁竞争时,需要等待其他线程释放锁,完了之后其他线程才能获取锁。

2. MVCC

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

作用:

MVCC 在MySQL InnoDB中的实现主要是为了提高数据库的并发性能,在并发情况下能保证数据的读写不会冲突,也能做到不加锁,非阻塞的并发读。

具体MVCC的详情就不多介绍了,可以看图理解理解。

MVCC机制

3.ReadView

ReadView其实就是上面提到的快照,每个事务在启动后第一次执行查询时会创建一份快照,一个事务快照的创建过程可以概括为:

查看当前所有的未提交并活跃的事务,存储在数组中

选取未提交并活跃的事务中最小的XID,记录在快照的xmin中

选取未提交事务中最大的XID,记录快照在xmax中

ReadView主要是用来做可见性判断的,即通过ReadView可以知道:哪些事务的提交结果对当前事务可见,哪些事务的提交结果对当前事务不可见。关于如何判断可见性,后面部分会对可见性判断算法做出介绍。

一致性的实现

什么是一致性?

一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性。

一致性也是原子性,隔离性,持久性最终追求的目标。

当事务在提交之前,发生原子性的问题,导致回滚,及时恢复,和在并发环境下的隔离性做到一致性。

mysql如何保证原子性和一致性

1.mysql原子性的保证是利用了undo log。undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

2.mysql持久性的保证是利用了redo log。Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。

3.mysql一致性的保证是从2个方面来保证的。从数据库层面来看,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。

但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!

    收藏