深入理解:脏读、不可重复读、幻读;事务隔离级别;Spring框架事务传播行为

本文主要是介绍深入理解:脏读、不可重复读、幻读;事务隔离级别;Spring框架事务传播行为,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

深入理解:脏读、不可重复读、幻读;事务隔离级别;Spring框架事务传播行为

  • 一·什么是事务?
  • 二·数据库表中的数据记录也分提交版本的(行级、表级),类似git的版本控制
  • 三·若没有事务隔离级别,多事务同时执行会出现什么问题?
  • 四·事务隔离级别概述:
    • 1.Read Uncommitted (读取未提交)
    • 2.Read Committed (读取已提交)
      • 2-1 场景描述:若a事务正在更新user表前100条数据,a事务还未结束;b事务就开始查询user全表数据,b事务是Read Committed (读取已提交)事务隔离级别
    • 3.Repeatable Read (可重复读)
      • 3-1 场景描述
    • 4.Serializable (序列化)
  • 五·多事务操作,读数据不准确问题,大致可以分为三类,:脏读、不可重复读、幻读
    • 1.脏读:问题解析
      • 1-1 注意重点:
      • 1-2 场景描述:
      • 1-3 解决办法:
    • 2.不可重复读:问题解析
      • 2-1 注意重点:
      • 2-2 场景描述:
      • 2-3 解决办法:
    • 3.幻读:问题解析
      • 3-1 注意重点:
      • 3-2 场景描述:
      • 3-3 解决办法:
  • 六·MySQL设置事务隔离级别方法:
    • 1.会话级别设置:
    • 2.全局级别设置:
    • 3.配置文件设置:
  • 七·Spring框架事务传播行为概念:
    • 1.以下是Spring中常见的几种事务传播行为:
      • (1)REQUIRED (默认值)
      • (2)REQUIRES_NEW
      • (3)SUPPORTS
      • (4)MANDATORY
      • (5)NOT_SUPPORTED
      • (6)NEVER
      • (7)NESTED
    • 2.两个携带事务注解方法互相调用,Spring事务执行流程:
    • 3.数据库本身是不支持事务嵌套执行的,Spring框架也是先后开启多个独立的事务,分别依照顺序执行提交或者回滚操作,给人感觉就像事务嵌套一样。
    • 4.Spring多层事务回滚机制,底层其实是利用数据库的保存点机制实现

一·什么是事务?

  1. 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。中途执行失败就会自动回滚到原本状态,事务由事务开始与事务结束之间执行的全部数据库操作组成。

二·数据库表中的数据记录也分提交版本的(行级、表级),类似git的版本控制

三·若没有事务隔离级别,多事务同时执行会出现什么问题?

  1. 因为多个事务同时操作相同数据库相同表时,可能出现一些数据不准确问题。而事务隔离级别则可以解决或者避免这些数据不准确问题。

  2. 举例说明:
    a事务正在对某部分数据进行操作期间,此时a事务还未结束,b事务就对相同部分数据进行了crud操作;此时a事务那边已经获取或者后续获取的数据,很可能就会跟实际数据库数据不一致,或者同个sql多次执行的结果不一致问题。

  3. 数据不准确问题,基本上都是读数据问题,增删改不存在这个问题。

四·事务隔离级别概述:

1.Read Uncommitted (读取未提交)

在这种最低级别的隔离下,一个事务可以读取到另一个事务尚未提交的数据变更。这意味着存在脏读、不可重复读、幻读问题:即事务读取到了其他事务还没有最终确认的数据。

2.Read Committed (读取已提交)

在这个级别,一个事务只能看到其他事务已经提交的数据。当事务开始时,它所能看到的数据是在那个时间点上所有已经提交的数据。这避免了脏读问题,但存在不可重复读、幻读问题,即在一个事务内,相同查询在不同时刻可能返回不同的结果,因为在此期间有其他事务提交了对数据的修改。

2-1 场景描述:若a事务正在更新user表前100条数据,a事务还未结束;b事务就开始查询user全表数据,b事务是Read Committed (读取已提交)事务隔离级别

1)a事务对这100条记录执行更新操作时,会对它们加排他锁(exclusive lock),阻止其他事务修改这些行。(2)b事务开始查询user全表数据,由于是Read Committed隔离级别,它会看到那些已经提交的数据变更,对于a事务尚未提交的更改,则不会看到;因此,b事务能够读取到user表的所有数据,包括a事务正在更新但尚未提交的前100条数据的以前提交版本,该100条正在更新的版本数据是读取不到的(3)在这个事务隔离级别下,b事务每次读取同一行数据时,都只能看到该行在读取时刻已经提交的最新版本,也就是说,如果在b事务进行查询的过程中,a事务提交了对某些行的更新,那么b事务在后续的查询中可能会看到不同的结果(即不可重复读问题)。但是,只要a事务未提交,b事务就能读取到这些行在a事务开始之前的状态。

注意:从Read Committed (读取已提交)开始,后续两个隔离级别也都是基于读取已提交

3.Repeatable Read (可重复读)

在这个级别,一旦事务开始,它在整个事务期间看到的数据视图是固定的,不会看到其他事务在这段时间内提交的任何更改,因此同一个事务内的多次读取操作会得到相同的结果,消除了脏读、不可重复读问题,但存在幻读问题。即同一事务在两次查询之间可能会看到新的行插入(或者满足条件的行数量发生变化),这些行是由其他事务在这两次查询之间提交的。

3-1 场景描述

1)假设有一个在线书店的库存表 BookStock,包含 ISBNQuantity 字段,表示每本书的库存数量。(2)事务A开始:
用户小明开启了一个事务A,想要查看一本图书的库存量。
事务A执行第一条SELECT语句:SELECT Quantity FROM BookStock WHERE ISBN = '1234567890',此时查询结果显示这本书的库存为10本。(3)事务B介入并修改数据:
在事务A未结束前,另一个事务B开始了,并且成功地将同本书减少了2本库存,然后事务B提交了这个更新操作。(4)事务A再次查询:
在事务A中,小明决定再次确认这本书的库存,以确保购买时库存充足,于是他再次执行了与步骤1中相同的查询语句:SELECT Quantity FROM BookStock WHERE ISBN = '1234567890'。
在可重复读隔离级别下,即使事务B已经提交了减少库存的操作,但事务A仍然只会看到第一次查询时的库存量,也就是10本,而不是实际的8本。(5)总结:这样,在整个事务A的生命周期内,针对同一本书的库存查询始终返回的是初始查询结果,这就实现了“可重复读”的特性。MySQLInnoDB存储引擎默认采用的就是可重复读隔离级别,并通过多版本并发控制(MVCC)来实现这一效果,尽管如此,需要注意的是在可重复读隔离级别下仍可能出现幻读现象(Phantom Reads),即新增满足查询条件的数据行。

4.Serializable (序列化)

这是最高的隔离级别,提供严格的事务隔离,相当于事务串行执行,从而避免了脏读、不可重复读以及幻读问题。为了达到这个级别,通常需要使用更为严格的锁策略,如范围锁或表级锁等,但这可能导致更高的锁竞争和更长的等待时间,从而影响并发性能

注意:实际应用中,不同的数据库系统可能会有不同的实现方式来达到上述的隔离级别,而且并非所有的数据库系统都完全支持所有级别的特性。例如,MySQL的InnoDB存储引擎通过多版本并发控制(MVCC)机制实现了Repeatable Read级别,并且可以通过Next-Key Locks来部分解决幻读问题;而Oracle数据库默认的事务隔离级别就是Read Committed。

五·多事务操作,读数据不准确问题,大致可以分为三类,:脏读、不可重复读、幻读

1.脏读:问题解析

脏读(Dirty Read)是数据库事务并发控制中的一种现象,指的是在一个事务内读取到了另一个未提交事务修改的数据,如果这个未提交事务最终选择回滚,那么之前读到的数据就是无效的、不一致的或者说是“脏”的。

1-1 注意重点:

1)一个事务内读取到另外多个未提交事务的数据
(2)最终引起第一个事务获取的数据跟数据库实际数据不一致

1-2 场景描述:

1)假设有一个银行转账表 BankTransaction,包含 FromAccount(转出账户)、ToAccount(转入账户)和 Amount(转账金额)字段。(2)事务A开始并修改数据:
用户张三开启了一个事务A,准备从自己的账户向李四转账 $1000。
事务A执行UPDATE语句:UPDATE BankTransaction SET Amount = Amount + 1000 WHERE ToAccount = 'LiSi',但是此时事务A还没有提交。(3)事务B介入并读取数据:
在事务A未提交的情况下,用户王五开启了事务B,并且查询了李四的账户余额,执行查询语句:SELECT SUM(Amount) FROM BankTransaction WHERE ToAccount = 'LiSi'。
由于隔离级别设置较低,事务B读取到了事务A尚未提交的转账操作,因此结果显示李四的账户多了 $1000。(4)事务A回滚:
然后,事务A因为某种原因(比如网络中断或用户取消操作),选择了回滚,即撤销了转账操作。(5)脏读结果:
这时,虽然事务A已经回滚,但事务B在之前看到的结果却是错误的——李四的账户实际上并没有增加 $1000,这就是一个典型的脏读场景。

1-3 解决办法:

在SQL标准中,通过将事务隔离级别设置为Read Committed及以上级别,可以避免脏读的现象发生。在这些较高的隔离级别下,一个事务只能读取到其他事务已经提交的数据。而在最低级别的Read Uncommitted(读取未提交)隔离级别下,就可能出现上述脏读问题。

2.不可重复读:问题解析

不可重复读(Non-repeatable Read)是事务并发控制中的一种现象,指的是在一个事务内,同一个查询语句在不同时刻执行两次或多次,却得到了不同的结果集。这是因为在该事务执行过程中,其他事务对数据进行了修改并提交

2-1 注意重点:

1)一个事务内读取到另外多个已提交事务的数据
(2)最终引起第一个事务内,同一个sql获取的字段值,前后不一致

2-2 场景描述:

1)事务A开始:
用户张三开启一个事务A,想要查看他的银行账户余额。
事务A执行第一条SELECT语句:SELECT Balance FROM BankAccount WHERE AccountId = 'ZhangSan',此时查询结果显示张三的账户余额为 $1000。(2)事务B介入并修改数据:
在事务A未结束前,另一个事务B开始了,并且成功地从张三的账户转账 $500 到李四的账户,然后事务B提交了这个转账操作。(3)事务A再次查询:
事务A还在进行中,同一用户张三决定再次确认一下自己的账户余额,于是执行了与步骤1中相同的查询语句:SELECT Balance FROM BankAccount WHERE AccountId = 'ZhangSan'。这次查询结果显示张三的账户余额为 500,而不是第一次查询时看到的1000。(4)总结:这就是典型的“不可重复读”情况,即在同一事务内部,同样的查询被执行了两次,但由于其他事务的影响,导致前后两次查询的结果不同。不可重复读问题在数据库隔离级别为Read Committed或更低级别的情况下可能发生。在SQL标准中,通过将事务隔离级别设置为Repeatable Read(可重复读)或者Serializable(序列化),可以避免不可重复读的现象发生。对于MySQL InnoDB存储引擎,默认事务隔离级别就是Repeatable Read,在此级别下使用MVCC机制来防止不可重复读的发生。

2-3 解决办法:

在SQL标准中,通过将事务隔离级别设置为Repeatable Read(可重复读)或者Serializable(序列化),可以避免脏读、不可重复读的现象发生。对于MySQL InnoDB存储引擎,默认事务隔离级别就是Repeatable Read,在此级别下使用MVCC机制来防止不可重复读的发生。

3.幻读:问题解析

幻读(Phantom Read)是数据库事务并发控制中的一种现象,它与脏读不同,发生在同一事务内多次执行相同的查询语句时,尽管每次查询条件相同,但是由于其他事务提交了新的数据插入或删除操作,导致前后两次查询结果的记录数量不一致,即出现了“幻影”行。

3-1 注意重点:

1)一个事务内读取到另外多个已提交事务的数据
(2)最终引起第一个事务内,同一个sql查询结果的记录数量,前后不一致。
(3)注意不是字段值不一致,是结果集数量不一致,这是幻读跟不可重复读的区别点

3-2 场景描述:

1)假设有一个图书借阅系统中的图书表 Books,包含 BookIDTitle 字段。(2)事务A开始并执行查询:
用户小明开启了一个事务A,并执行了一条查询语句来查看某类图书的数量:SELECT COUNT(*) FROM Books WHERE Category = 'Fiction',得到的结果为50本小说。(3)事务B介入并插入数据:
在事务A未结束前,另一个事务B开始了,事务B成功地将一本新的小说添加到了Books表中,类别也为Fiction,然后事务B提交了这个插入操作。(4)事务A再次执行相同的查询:
事务A仍在进行中,小明决定再次确认一下此类别图书的数量,执行了与步骤1中相同的查询:SELECT COUNT(*) FROM Books WHERE Category = 'Fiction'。这次查询结果显示的小说数量为51本,而不是第一次查询时看到的50本。(5)这就是典型的幻读场景,即使在同一事务内使用相同的查询条件,由于其他事务提交的新插入操作,使得结果集在事务A看来似乎出现了“幻影”的行。

3-3 解决办法:

在SQL标准中,通过将事务隔离级别设置为Serializable(序列化),可以避免脏读、不可重复读、幻读的现象发生。

六·MySQL设置事务隔离级别方法:

sql语法格式:

SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level;

其中 level 可以是以下值之一:

READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ (MySQL InnoDB引擎的默认隔离级别)
SERIALIZABLE

1.会话级别设置:

通过SQL命令设置当前会话(Session)的事务隔离级别。这种方式只对当前连接有效。

示例:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

2.全局级别设置:

要改变所有新建立的会话的默认隔离级别,可以使用 GLOBAL 关键字,但这通常需要具有管理员权限,并且重启服务器后该设置会失效。

示例:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

3.配置文件设置:

若要永久性地设置整个MySQL服务的默认事务隔离级别,可以在MySQL的配置文件(如my.cnf或my.ini)中添加以下行:

[mysqld]
transaction-isolation = READ-COMMITTED

然后需要重启MySQL服务以使更改生效。

七·Spring框架事务传播行为概念:

(1) 数据库并没有传播行为这个概念,在使用Spring框架进行事务管理时,确实存在一个与之类似的术语——事务传播行为(Transaction Propagation)

(2) 事务传播行为是指在一个方法调用链中,当一个事务方法被另一个事务方法调用时,如何处理这两个方法之间的事务边界和事务一致性的问题

(3) 在Spring框架中,通过@Transactional注解的propagation属性可以指定被标注的事务方法,在被其他方法调用时,该如何开启事务操作

1.以下是Spring中常见的几种事务传播行为:

示例代码:

@Transactional(propagation = Propagation.REQUIRED)
public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);System.out.println(1/0);
}

(1)REQUIRED (默认值)

如果当前没有事务,则新建一个事务。
如果当前存在事务,则加入到该事务中执行。
这种情况下的所有数据库操作都在同一事务中完成。

(2)REQUIRES_NEW

总是开启一个新的事务,如果当前存在事务,则将当前事务挂起(暂停)。
新事务与当前事务完全独立,即使外部事务回滚,新事务也会提交。
通常用于需要确保业务逻辑隔离性或者数据一致性的场景。

(3)SUPPORTS

如果当前存在事务,则在该事务内运行;
若当前不存在事务,则以非事务方式运行。
不主动创建事务,仅支持已有的事务上下文。

(4)MANDATORY

必须在一个已存在的事务中运行,否则抛出异常。
如果当前存在事务,则加入到该事务中执行;
若不存在事务,则抛出异常。

(5)NOT_SUPPORTED

完全不支持事务,如果当前存在事务,则把当前事务挂起。
执行时不使用任何事务上下文,即以非事务方式运行。

(6)NEVER

必须在非事务环境下执行,如果当前存在事务,则抛出异常。
禁止在事务上下文中运行。

(7)NESTED

如果当前存在事务,则在嵌套事务内执行。
嵌套事务可以独立于外层事务进行提交或回滚,而不会影响外层事务的状态,但最终还是受外层事务的约束,也就是说,如果外层事务回滚,那么嵌套事务所做的任何更改也将被回滚。

注意:每种传播行为都适用于特定的业务场景和并发控制需求,开发者应根据实际情况选择合适的事务传播行为,以保证数据的一致性和系统的正确运作。

2.两个携带事务注解方法互相调用,Spring事务执行流程:

示例:Spring框架中,一个@Transactional方法a调用另一个@Transactional方法b,假如a、b事务传播行为都是REQUIRES_NEW,a、b方法是如何根据传播行为,进行协调事务执行

1)方法a开始执行:
当调用方法a时,由于其传播行为设置为REQUIRES_NEWSpring会首先检查当前是否有事务。即使在方法a之前存在一个事务(假设为Transaction T0),Spring也会暂时挂起这个外部事务,并开启一个新的、与T0独立的事务(Transaction T1)。(2)方法a调用方法b:
在方法a内部调用带有同样传播行为REQUIRES_NEW的方法b。
即使方法a本身正处于Transaction T1中,Spring仍然会创建一个新的、与T1完全独立的事务(Transaction T2)来运行方法b。(3)方法b执行其事务:
方法b在其新创建的事务T2内执行所有数据库操作。如果方法b内部出现异常并且未被捕获,则仅T2会被回滚,而不会影响到方法a的事务T1或更外层的任何事务。(4)方法b结束并提交或回滚事务:
如果方法b正常完成(没有未捕获异常),则T2会被提交,它的更改对其他事务立即可见。无论方法b的结果如何,方法a所在的事务T1仍保持活跃状态。(5)方法a继续执行及事务处理:
不管方法b的结果如何,方法a在事务T1下继续执行其剩余逻辑。如果方法a后续也正常完成,则T1将被提交;如果发生未捕获的非检查型异常,T1将会回滚,而不影响已经提交了的T2。(6)恢复外层事务(如果有):
方法a的事务T1提交或回滚后,若存在先前挂起的外层事务T0,那么T0将被恢复并根据自身情况决定是否继续执行或者回滚。

总结:在这种情况下,每个方法都将在自己的全新事务中执行,即使它们互相嵌套调用。这种方式确保了各个方法的事务隔离性,且各自的操作结果互不影响,除非通过共享数据等方式显式关联。

3.数据库本身是不支持事务嵌套执行的,Spring框架也是先后开启多个独立的事务,分别依照顺序执行提交或者回滚操作,给人感觉就像事务嵌套一样。

4.Spring多层事务回滚机制,底层其实是利用数据库的保存点机制实现

这篇关于深入理解:脏读、不可重复读、幻读;事务隔离级别;Spring框架事务传播行为的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/609490

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让