Spring事务问题,同一次请求中相同SQL查询结果不一致


 前言:

       为什么面试的时候会被问题事务相关的问题,比如事务隔离性,spring事务等。因为工作中确实会遇到啊,不了解你就处理不了遇到问题


1.问题现象

      上周同事让我帮他看一个问题. 他说同一个SQL 两次执行后,查询结果不一致, 两次查询一次有记录一次没有记录。我看他演示了一篇,演示内容如下

  • 在业务处理中查询SQL设置了一个断点
  • 发布一次业务请求, 业务处理后,程序第一次运行到断点时,查看结果集中存在1条记录
  • 继续执行业务处理
  • 程序再次执行到查询SQL的断点时,查看结果集中集合为空集合
  • 将查询的SQL截取处理,在数据库中运行,查询结果有1条所需的数据

2.问题分析

    分析问题前需要先了解数据库事务的一些知识

2.1认识事务

       事务在数据库系统中主要用于保存数据的一致性,防止数据出现错误。事务是一组包含一条或者多条语句的处理逻辑单元。 事务内的语句都会认为属于同一个处理单元, 处理过程无问题则同时成功,如果处理过程中存在异常则同时失败。

2.2事务的CAID特性

特性
1原子性 (Atomicity)
  • 事务的动作要么全部执行要么都不执行 all-or-nothing
  • 事务回复和瘫痪恢复
2一致性 (Consistency)
  • 正确性,能够正确的将数据库从一个一致状态切换到另一个一致状态,最常见的模型是银行转账模型。 
3隔离性 (lsolation)
  • 并发事务之间不能相互干扰,一个事务操作的数据不会被其他事务看到和操作
  • 多个正在运行的事务只能看到相同的数据库数据和自己的事务内的信息,只有在事务完成后才能被所有使用者看到
4持久性 (Duration)
  • 如果一个事务已经提交,那么它对的结果是永久被修改,不会回到修改之前

2.3事务隔离

    这个问题和事务的隔离性有关系, 事务的隔离性就需要提到事务的隔离级别

  • READ UNCOMMITTED(读未提交数据): 允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
  • READ COMMITTED(读已提交数据): 只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
  • REPEATABLE READ(可重复读): 确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
  • SERIALIZABLE(序列化): 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

不可重复读(read-committed)

可重复读(repeatable-read)

串行化(serializable)

       

       Oracle支持两种事务隔离级别:READ COMMITTED(读已提交为默认事务隔离级别)和 SERIALIZABLE,MySql 默认的事务隔离级别为REPEATABLE READ,从这里可以看出比较MySQL, Oracle 会出现“不可重复读” 的问题
 

2.4 Spring事务

       Spring事务默认使用数据库事务,当一次请求发起后,调用Spring的某个带有事务的Service时候,会开启一个事务,当执行完成后事务COMMIT提交

Spring事务的传播属性列表

属性描述
1REQUIRED(默认)

如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务

2MANDATORY支持当前事务,如果当前没有事务,就抛出异常
3NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
4NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
5REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
6SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行
7NESTED 支持当前事务,新增Savepoint点,与当前事务同步提交或回滚

Spring事务隔离级别列表

属性描述

1

DEFAULT

(默认) 

使用数据库默认的事务隔离级别

2

READ_UNCOMMITTED (读未提交)

这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读

3

READ_COMMITTED

(读已提交) 

保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读

4

REPEATABLE_READ

(可重复读)

这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读

5

SERIALIZABLE

(串行化) 

这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读

3 问题原因

   
       了解上面的知识点后,回头看同事提到的的问题。

       一次请求后调用Spring框架中的某个服务接口,Spring框架使用事务管理器管理请求产生的事务, Spring事务管理器默认使用REQUIRED事务传播,这样此次请求的所有数据库访问都是同一个事务中处理。 Spring事务管理默认使用数据库事务,当前数据库使用的是Oracle, Oracle的事务隔离级别为“读已提交”,说明只有事务COMMIT后才能被其他事务读到,事务COMMIT之前,其他事务无法读到本次修改的数据。  

         第二次查询结果为空集合, 可能是由于修改的数据引起SQL中的某些条件不满足,造成查询结果为空, 而其他的事物由于看不到修改所以能查询到数据,

      分析代码。 结果和想的相同。  梳理下问题产生的过程如下图所示:

  1. Service服务接口开始处理并开启了一个事务T,事务T开始时查询数据库数据此时获取结果为R,
  2. 业务过程中修改过表中数据D,将某个字段设置为空, 因事务隔离性只有事务T能看到修改内容,此时再使用之前的SQL查询,因为数据发生了变化,字段空让数据R在查询时被被过滤,所以第二次查询不到数据。
  3. 因为事务T的事务没有提交。数据库中的数据还是修改之前的数据, 所以其他事务在数据库中执行查询SQL,还能查询出第一次查询看到的数据

                     

    

上一篇:Spring 中的@Import注解分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值