百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

详解MySQL事务原理

wptr33 2025-01-11 17:49 25 浏览

今天给大家分享的是大数据开发基础部分MySQL的事务,事务在MySQL知识点中非常重要的部分,很多伙伴只是知道MySQL的四大特性,但不知道其中的原理,老刘这次给大家详细的描述MySQL四大特性的原理,MySQL事务篇的大纲如下:

什么是事务?

在MySQL中的事务是由存储引擎实现的,而且支持事务的存储引擎不多,我们主要讲解InnoDB存储引擎中的事务。

事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。

事务用来管理 DDL、DML、DCL 操作,比如 insert,update,delete 语句,默认是自动提交的。

事务的四大特性(ACID)

  1. Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部成功,要么全部失败。
  2. Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的,就是说事务开始和结束后,数据库的完整性不会被破坏。
  3. Isolation(隔离性):事务之间不会相互影响。由锁机制和MVCC机制来实现的,其中MVCC(多版本并发控制):优化读写性能(读不加锁、读写不冲突),四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。
  4. Durability(持久性):事务执行成功后必须全部写入磁盘,事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。

事务的使用

begin或start transaction:开启一个事务;

commit:提交一个事务,并使已对数据库进行的所有修改称为永久性的;

rollback:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

ACID实现原理

下面我们就来详细讲解一下上述示例涉及的事务的ACID特性的具体实现原理。总结来说,事务的隔离性由多版本控制机制和锁实现,而原子性、一致性和持久性通过InnoDB的redo log、undo log和ForceLog at Commit机制来实现。

重做日志Redo Log

如果要存储数据则先存储数据的日志,一旦内存崩了,则可以从日志找重做日志保证了数据的可靠性,InnoDB采用了Write Ahead Log(预写日志)策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。

回滚日志Undo Log

数据库崩溃重启后需要从redo log中把未落盘的脏页数据恢复出来,重新写入磁盘,保证用户的数据不丢失。当然,在崩溃恢复中还需要回滚没有提交的事务。由于回滚操作需要undo日志的支持,undo日志的完整性和可靠性需要redo日志来保证,所以崩溃恢复先做redo恢复数据,然后做undo回滚。

所以,在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。

Force Log at Commit机制

它实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,然后事务的提交操作完成才算完成。为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作(操作系统),将缓冲文件从文件系统缓存中真正写入磁盘。

总结一下就是redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也可以用于多版本控制。而Force Log at Commit机制保证事务提交后redo log日志都已经持久化。

原子性

原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。例如银行转账要么成功,要么失败,是不存在中间的状态!

Undo Log是实现原子性的关键,靠的就是undo log。当事务对数据库进行修改时,InnoDB会生成对应的undo log。undo log它属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。

以update操作为例:当事务执行 update 时,其生成的 undo log 中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到 update 之前的状态。

持久性

持久性是指事务执行成功后必须全部写入磁盘,事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。

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策略,所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

隔离性

在MySQL隔离性中,一般有两种情况:

  1. 要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。
  2. 在进行读操作的时候,可能出现脏读、不可重复读、幻读的问题。

首先讲第一种情况,MySQL要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。

锁机制的基本原理可以理解为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。

至于锁机制中的锁,一般就是之前讲到的MySQL锁,大家可以去看看这篇MySQL锁的内容。

接着讲第二种情况,读操作可能出现脏读、不可重复读、幻读的问题。

隔离性追求的是并发情形下事务之间互不干扰,但是在事务的并发操作中可能会出现一些问题:

  1. 丢失更新:两个事务针对同一数据都发生修改操作时,会存在丢失更新问题。
  2. 脏读:对于两个事务 T1,T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后,若 T2 回滚,T1读取的内容就是临时且无效的。
  3. 不可重复读:对于两个事务T1,T2,T1 读取了一个字段,然后 T2 更新了该字段。之后,T1再次读取同一个字段,发现字段的内容不一样。要求,多次读取数据的时候,在一个事务中读出的都应该是一样的。一般是由于 update 操作引发,所以将来执行的时候要特别注意。
  4. 幻读:对于两个事务T1,T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。之后。如果 T1 再次读取同一个表,就会多出几行。就是发现数据的数量不一样。要求,在一个事务中多次去读取数据的时候都应该是一样的。

虽然有上述这些问题,但MySQL数据库为我们提供的四种隔离级别(由低到高):

  1. Read uncommitted (读未提交):最低级别,任何情况都无法保证。
  2. Read committed (RC,读已提交):可避免脏读的发生。
  3. Repeatable read (RR,可重复读):可避免脏读、不可重复读的发生。(InnoDB默认级别为RR,它可以解决幻读,主要原因是Next-Key(Gap)锁,只有RR才能使用Next-Key锁)
  4. Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

解决脏读、不可重复读、幻读的问题使用的是MVCC,即多版本的并发控制协议。它说的就是在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)。

MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列( 也可以称之为标记位 )和undo log。其中数据的隐藏列包括了该行数据的版本号、删除时间、指向undo log的指针等等;当读取数据时,MySQL可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现MVCC。

MVCC如何解决脏读、不可重复读、幻读的问题

1、MVCC解决脏读

当事务T1在第三个时刻读取自己的余额时,会发现数据已被T2事务修改,并且T2的状态还没有提交。此时事务A读取最新数据后,根据数据的undo log执行回滚操作,得到事务T2修改前的数据,从而避免了脏读。

2、MVCC解决不可重复读

当事务T1在第二个时刻第一次读取数据时,会记录该数据的版本号(数据的版本号是以row为单位记录的),假设版本号为1;当事务T2对自己的余额进行修改并且提交时,该行记录的版本号增加,假设版本号为2;当事务T1在第五个时刻再一次读取数据时,发现数据的版本号2大于第一次读取时记录的版本号1,因此会根据undo log执行回滚操作,得到版本号为1时的数据,从而实现了可重复读。

3、MVCC解决幻读

InnoDB实现的RR通过next-key lock机制避免了幻读现象。

next-key lock是行锁的一种,实现相当于record lock(记录锁) + gap lock(间隙锁),它的特点是不仅会锁住记录本身(record lock的功能),还会锁定一个范围(gap lock的功能)。

当事务T1在第二个时刻第一次读取0<id<5数据时,会进行标记,标记内容包括数据的版本号等,并且标记的不只是id=1的数据,还将范围(0,5)进行了标记。我们接着在第三个时刻插入新的用户并且提交事务,最后第五个时刻再次读取0<id<5数据时,便可以发现id=2的数据比之前标记的版本号更高,此时再结合undo log执行回滚操作,避免了幻读。

稍微总结下,InnoDB通过锁机制、数据的隐藏列、undo log和类next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要。不过需要说明的是,RR虽然避免了幻读问题,但是毕竟不是Serializable,不能保证完全的隔离。

一致性

一致性是事物追求的最终目标,前面提到的原子性,隔离性,持久性都是为了保证数据库的一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。

总结

本文作为大数据开发指南MySQL的第四篇详细介绍了MySQL事务的内容,尤其是MySQL四大特性的原理。希望大家能够跟着老刘的文章,好好捋捋思路,争取能够用自己的话把这些知识点讲述出来!

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...