理解事务四大特性(Transaction)——原子性、一致性、隔离性和持久性(ACID)

article/2025/8/23 22:05:36

事务是指对系统进行的一组操作,为了保证系统的完整性,事务需要具有ACID特性,具体如下:

1. 原子性(Atomic)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。
回滚实际上是一个比较高层抽象的概念,大多数DB在实现事务时,是在事务操作的数据快照上进行的(比如,MVCC),并不修改实际的数据,如果有错并不会提交,所以很自然的支持回滚。
而在其他支持简单事务的系统中,不会在快照上更新,而直接操作实际数据。可以先预演一边所有要执行的操作,如果失败则这些操作不会被执行,通过这种方式很简单的实现了原子性。
针对同一个事务
在这里插入图片描述
这个过程包含两个步骤
A: 800 - 200 = 600
B: 200 + 200 = 400

原子性表示,这两个步骤一起成功,或者一起失败,不能只发生其中一个动作

2. 一致性(Consistency)
事务前后数据的完整性必须保持一致。
一致性是指事务使得系统从一个一致的状态转换到另一个一致状态。事务的一致性决定了一个系统设计和实现的复杂度。事务可以不同程度的一致性:
强一致性:读操作可以立即读到提交的更新操作。
弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。
最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
其他一致性变体还有:
单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。
在这里插入图片描述
操作前A:800,B:200
操作后A:600,B:400

一致性表示事务完成后,符合逻辑运算
3. 隔离性(Isolation)
并发事务之间互相影响的程度,比如一个事务会不会读取到另一个未提交的事务修改的数据。
在这里插入图片描述两个事务同时进行,其中一个事务读取到另外一个事务还没有提交的数据。
在这里插入图片描述
按照箭头顺序,如果没有隔离性,那么按照箭头顺序就可能会出现脏读,也就是A:800-100,B:200+100.
再看一个明了例子:
  小明和小芳各自有一本作业本,如果他们同时去写作业,这时他们都可以在各自作业本上写作业是相互不影响的。但是如果他们两个人只有一本作业本,但是他们都想去写作业怎么办,那么就这个时候就只能等一个人先写完作业后,另外一个人才能写,要不然两个人同时在同一个作业本上写作业,那么肯定会乱套。所以这种两个事物操作同一个对象必须隔离开来不能相互影响的特性称为事务的隔离性。
在事务并发操作时,可能出现的问题有:
脏读:事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
在这里插入图片描述
不可重复读:在同一个事务中,两条相同的查询语句的查询结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这会导致锁竞争加剧,影响性能。另一种方法是通过MVCC可以在无锁的情况下,避免不可重复读。
在这里插入图片描述
幻读:同一个事务内,两条相同的查询语句的查询结果应该相同。但是,如果另一个事务同时提交了新数据,当本事务再更新时,就会惊奇地发现这些新数据,貌似之前读取到的数据是“鬼影”一样的幻觉。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的,解决方法有两个:
1、将事务隔离级别设置为serializable可以避免幻读现象,但串行化导致无并发,效率降低。
2、保持事务的隔离级别RR(可重复读)不变,利用间隙锁的特点,对查询结果集施加共享锁或者排他锁。
在这里插入图片描述

幻读现象与不可重复读现象不同之处在于,幻读现象读不到其他事务已经提交的数据,而不可重复读现象读到的是其他事务已经提交的数据。

上述事务机制不能很好解释幻读现象读取不到其他事务已经提交的数据现象,我们通过一个例子来验证:
在这里插入图片描述

事务的隔离级别从低到高有:
Read Uncommitted:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
Read Committed:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
Repeated Read:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
通常,在工程实践中,为了性能的考虑会对隔离性进行折中。
4. 持久性(Durability)
事务提交后,对系统的影响是永久的。
表示事务结束后的数据不随着外界原因导致数据丢失

操作前A:800,B:200
操作后A:600,B:400
如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:800,B:200
如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:600,B:400

最后我们看下一个问题:spring的事务是什么?与数据库的事务是否一样?

  之前一直觉得事务只针对于数据库当中,5种隔离级别,7种传播行为,后来才发现这是针对Spring的,对数据库来说隔离级别只有4种,Spring多了一个DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.

  总的来说,本质上其实是同一个概念,spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的。数据库的事务说简单就只有开启,回滚和关闭,spring对数据库事务的包装,原理就是拿一个数据连接,根据spring的事务配置,操作这个数据连接对数据库进行事务开启,回滚或关闭操作。但是spring除了实现这些,还配合spring的传播行为对事务进行了更广泛的管理.其实这里还有个重要的点,那就是事务中涉及的隔离级别,以及spring如何对数据库的隔离级别进行封装。事务与隔离级别放在一起理解会更好些。

四种隔离级别,从程度从低到高依次是读未提交,读已提交,可重复读、串行化。隔离级别越大安全性越高,同时效率也越低,如果设置串行化,那就不存在并发操作了。四种隔离级别对于上述并发下可能出现的三个问题的控制情况可用一张图来描述:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

spring事务与数据库事务与锁之间的关系?

  spring事务本质上使用数据库事务,而数据库事务本质上使用数据库锁,所以spring事务本质上使用数据库锁,开启spring事务意味着使用数据库锁;那么事务的隔离级别与锁有什么关系呢?本人认为事务的隔离级别是通过锁的机制实现的,事务的隔离级别是数据库开发商根据业务逻辑的实际需要定义的一组锁的使用策略。当我们将数据库的隔离级别定义为某一级别后如仍不能满足要求,我们可以自定义 sql 的锁来覆盖事务隔离级别默认的锁机制。

spring事务实际使用AOP拦截注解方法,然后使用动态代理处理事务方法,捕获处理过程中的异常,spring事务其实是把异常交给spring处理;

spring事务只有捕获到异常才会终止或回滚,如果你在程序中try/catch后自己处理异常而没有throw,那么事务将不会终止或回滚,失去事务本来的作用;

spring事务会捕获所有的异常,但只会回滚数据库相关的操作,并且只有在声明了rollbackForClassName='Exception’之类的配置才会回滚;

spring事务会回滚同一事务中的所有数据库操作,本质上是回滚同一数据库连接上的数据库操作;

spring事务总结:

  • spring事务本质上使用数据库锁;
  • spring事务只有在方法执行过程中出现异常才会回滚,并且只回滚数据库相关的操作;

事务并发异常-丢失更新
如果多个线程操作,基于同一个查询结果对表中的记录进行修改,那么后修改的记录将会覆盖前面修改的记录,前面的修改丢失掉了,这就叫丢失更新。
丢失更新分为两类。第一类丢失更新回滚丢失第二类丢失更新覆盖丢失。SQL92没有定义这种现象,标准定义的所有隔离级别都不允许第一类丢失更新发生。解决丢失更新的办法就是加锁。

悲观锁机制
假定这样的问题是高概率的,最好一开始就锁住,免得更新老出错。

添加共享锁:select * from account lock in share mode;
添加排他锁:select * from account for update;
乐观锁机制
假定这样的问题是小概率的,最后一步做更新的时候再锁住,免得锁住时间太长影响其他人操作。

在表中增加timestamp类型字段,在执行插入或更新时记录最新时间到该字段上;
在修改数据时检查timestamp类型的字段是否改变判断当前的更新基于的查询是否已经过时。
在用户并发数比较少且冲突比较严重的应用系统建议选择悲观锁中的排他锁,其他情况首选乐观锁。
参考地址:
1、理解事务(ACID)——原子性、一致性、隔离性和持久性
2、事务ACID理解
3、spring的事务是什么?与数据库的事务是否一样


http://chatgpt.dhexx.cn/article/eNaRcUVp.shtml

相关文章

持久性连接和非持久性连接

HTTP连接有两种,一种为持久性连接;另一种为非持久性连接。 由于不同的HTTP版本,使用不同的方式。 在这里分析一下二者的区别: 一、非持久性连接(Nonpersistent HTTP) 特点:每个TCP连接最多允许传输一个对象 HTTP 1.0使用的非持久性连接 过程: 响应时间分析与建…

mysql事务如何保证持久性_详解MySQL事务持久性实现

所谓MySQL事务持久性就是事务一旦提交,就是永久性的,不会因为宕机等故障导致数据丢失(外力影响不保证,比如磁盘损害)。持久性是保证了MySQL数据库的高可靠性(High Reliability),而不是高可用性(Hign Availability)。 MySQL的innoDB存储引擎,使用Redo log保证了事务的持久性…

Mysql持久性的实现

1、持久性的定义 事务一旦提交,则其所有的修改将会保存到数据库当中。即使此时系统崩溃,修改的数据也不会丢失。同时数据库连接中,默认有一个参数autocommit1(如果想要关掉,要set autocommit0,然后要手动的开启关闭&a…

数据库事务-持久性原理

持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。(持久性由redo log日志来保证) 以一个跟新语句执行流为例: 在存储引擎执行时,会先在缓存池…

MYSQL 1251

今天折腾mysql,,一直在连接的时候出现1251的报错,然后百度了很多方法,都没有办法成功,,最后折腾了好久,,终于成功了,进入MySQL 8.0 Command Line Clien,依次输…

java_1125

1。从键盘输入一个字符串 编写一个程序,判断输出一个字符串中大写英文字母数,和小写英文字母数,和其他非英文字母数 2. 编写一个方法,返回一个double类型的二维数组,数组中的元素通过解析字符串参数获得&#xff0c…

java_1115

定义一个接口 MediaPlayer,表示家庭影院的一个设备。MediaPlayer 中 包含 play(),stop(), open()三个方法,分别表示播放、停止和开仓功能。 MediaPlayer 有三个实现类,分别为: DVDPlayer,表示 …

java--Integer的128陷阱

包装类 提到128陷阱就不得先说一下包装类 1.为什么有包装类 在面向对象中,“一切皆对象”,但基本数据类型的数据不太符合这一理念,基本数据类型不是对象.涉及到类型之间的转化,数据类型之间的基本操作;如果都有我们…

P1152 java

package suanfa_xiaoqiang1; import java.util.Arrays; import java.util.Scanner; public class P1152 { public static void main(String[] args) { Scanner sc new Scanner(System.in); int nsc.nextInt(); int[] anew int[n1]; //数组遍历从1开始的时候,要加…

Java(11)

学习来源:日撸 Java 三百行(21-30天,树与二叉树) 第 28 天: Huffman 编码 (节点定义与文件读取) 输入:输入表示文本文件的字符串paraFilename 输出:构造对象tempHuffman并输出文本文件的内容inputText 优…

Java-1110

https://github.com/Lannister-never-pay/JavaWebLearning/tree/main/java1108 因为懒&#xff0c;还是用的1108的module JSP 指令 作用&#xff1a;用于配置JSP页面&#xff0c;导入资源文件 格式&#xff1a;<% 指令名称 属性名1属性值1 属性名2属性值2 %> 多个键值…

Java——详解Integer128陷阱

今天我们来一起探讨一下Java的128陷阱 首先我们通过代码对128陷阱进行一个认知 public static void main(String[] args){Integer a 127 ;Integer b 127 ;Integer c 128 ;Integer d 128 ;Integer e 1000 ;Integer f 1000 ;int a1 127;int b1 127;int c1 128;int d1 …

Java-11

学习来源&#xff1a;日撸 Java 三百行&#xff08;31-40天&#xff0c;图&#xff09;_闵帆的博客-CSDN博客 36 邻接表 36.1 相当于图的压缩存储. 每一行数据用一个单链表存储。 36.2 重写了广度优先遍历. 可以发现, 使用队列的机制不变. 仅仅是把其中的 for 循环换成了 wh…

JAVA101-135

JAVA101-150 字符串StringBuilder链式编程简化代码对应的关系可以使用查表法&#xff0c;通过数组的对应的下表来改变成相应的值 修改字符串字符串变整数重点&#xff1a;字符串变为数组 ArrayList集合的基本使用集合一开始的长度为0&#xff0c;如果用循环&#xff0c;进不去 …

Java-1214

Spring5总体学习内容 Spring基本概念IOC容器AopJdbcTemplate事务管理Spring5新特性 框架概述 Spring是轻量级的开源的JavaEE框架Spring可以解决企业应用开发的复杂性Spring有两个核心部分&#xff1a;IOC、Aop IOC&#xff1a;控制反转&#xff0c;把创建对象的过程交给Spri…

下载Google Play外国区APP技巧

安卓用户若遇到喜欢的APP是外国区的&#xff0c;只要翻墙就能下载。比起果粉还要注册&#xff0c;是简便很多。但有没有更简单的办法&#xff1f;这个必须有&#xff01;笔者前几天在网上闲逛时&#xff0c;就发现了一个给力的网站。让你不用翻墙&#xff0c;只需3个步骤&#…

Google Play国内应用市场发布版本步骤指导

应用发布步骤指导 前言Google Play华为小米Vivooppo 博客创建时间&#xff1a;2022.08.19 博客更新时间&#xff1a;2022.08.22 以Android studio build7.0.0&#xff0c;SDKVersion 31来分析讲解。如图文和网上其他资料不一致&#xff0c;可能是别的资料版本较低而已。 前言 …

Google Play App Signing的问题以及解决方式

Google Play App Signing是Google Play 的应用签名&#xff0c;在Google Play上创建项目的时候如果勾选了它&#xff0c;那么它就会生成一个签名文件&#xff0c;不管你上传到Google Play的apk是否用你的签名文件打包&#xff0c;最终都会被替换成Google Play App Signing里的签…

如何将Flutter开发的Android app 发布Google Play(谷歌应用商店)流程

将Flutter Android app 发布Google Play&#xff08;谷歌应用商店&#xff09;流程 一、首先就是要做到科学&#xff01; 二、打开google play官网&#xff0c;注册谷歌账号 三、打开谷歌开发者站点https://play.google.com/apps/publish/signup/创建你的App应用 四、创建完…

h5/uni-app打开手机app,没有则跳转到商店下载

需求&#xff1a;在做商品分享/直播分享时&#xff0c;app内分享出去的链接&#xff0c;能够在微信、手机浏览器打开。 遇到的问题&#xff1a; 1&#xff0c;Android&#xff0c;当手机没有下载app时&#xff0c;在浏览器打开&#xff0c;会下载app&#xff0c;但是手机下载了…