一个事务内修改后,再查询,结果如何

本文探讨了 MySQL 8.0 在可重复读隔离级别下,同一事务中进行数据更新后再查询时遇到的问题。即更新后的数据无法被后续的查询所识别的现象。

数据库:mysql8.0
级别:可重复读

情况:同一个事务内,多个操作
操作一:更新已存在的某些数据
操作二:查询这部分数据

结果:查询到的结果是上个操作已更新的,但是数据库查询查询不到

在 Spring 框架中,当在一个事务方法中更新数据后立即查询数据,却无法获取到最新的更改,这种现象通常与 **事务的隔离级别**、**数据库连接的可见性**、**缓存机制** 以及 **事务传播行为** 等因素有关。以下是对此问题的详细分析与解决方案。 ### 事务隔离级别影响数据可见性 Spring 中的事务默认使用数据库的默认隔离级别(通常是 `READ COMMITTED` 或 `REPEATABLE READ`),不同隔离级别决定了事务中对数据修改可见性。例如,在 `REPEATABLE READ` 隔离级别下,一个事务中多次查询同一数据,其结果保持一致,即使其他事务提交了修改,也不会立即反映到当前事务中。 ```java @Transactional(isolation = Isolation.REPEATABLE_READ) public void updateAndQuery() { updateData(); queryData(); // 可能查不到最新数据 } ``` 在此隔离级别下,事务的多次查询结果保持一致,因此即使更新操作成功,后续查询仍可能返回旧值[^1]。 ### 事务未提交导致查询不到最新数据 如果更新操作和查询操作处于同一个事务中,但查询操作发生在更新操作之后、事务提交之前,由于数据事务的隔离性,查询可能仍然读取到的是旧数据数据库在事务提交前,其他操作(即使是同一个事务内的)可能无法立即看到更改。 ```java @Transactional public void updateThenQuery() { updateData(); // 更新数据 queryData(); // 查询数据,可能看不到更新 } ``` 这种情况是由于事务尚未提交,数据库的事务日志尚未刷新到数据表中,因此查询操作无法读取到最新的更改[^3]。 ### 二级缓存或一级缓存导致查询结果未更新 Spring 与 MyBatis 等 ORM 框架结合时,可能会使用到一级缓存或二级缓存。如果更新操作后查询操作命中了缓存,那么即使数据库中的数据已经发生变化,查询结果仍然可能返回旧值。 ```java @Transactional public void updateAndQueryWithMyBatis() { myMapper.updateRecord(); Record record = myMapper.selectById(1); // 可能从缓存中读取旧数据 } ``` MyBatis 的一级缓存默认开启,作用域为 SqlSession,若在同一个 SqlSession 中执行更新和查询,MyBatis 会清空缓存,但如果使用了自定义的缓存实现或二级缓存,则需手动清除缓存以确保数据一致性[^2]。 ### 异步操作导致查询事务提交前执行 如果更新操作后,立即启动一个异步线程执行查询操作,而事务尚未提交,异步线程查询数据将无法看到未提交的更改。 ```java @Transactional public void updateAndAsyncQuery() { updateData(); new Thread(() -> { queryData(); // 异步查询事务未提交,查不到最新数据 }).start(); } ``` 在这种情况下,异步线程访问数据库时,主事务尚未提交,因此查询操作无法看到未提交的更新。 ### 解决方案 #### 1. 明确事务边界并手动提交 如果希望在更新后立即查询到最新数据,可以将更新操作和查询操作分别放在不同的事务中,确保更新事务已提交。 ```java @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateData() { // 更新数据 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void queryData() { // 查询最新数据 } ``` 通过 `REQUIRES_NEW` 传播行为,确保每次方法调用都开启一个事务,并在方法执行结束后提交事务,从而保证查询操作能读取到已提交的最新数据。 #### 2. 设置合适的事务隔离级别 根据业务需求,可以将事务的隔离级别设置为 `READ COMMITTED` 或 `READ UNCOMMITTED`,前者允许读取其他事务已提交的数据,后者允许读取未提交的数据(脏读)。 ```java @Transactional(isolation = Isolation.READ_COMMITTED) public void updateAndQuery() { updateData(); queryData(); // 查询到已提交的最新数据 } ``` 注意:使用 `READ UNCOMMITTED` 时需谨慎,因为它可能导致脏读,影响数据一致性。 #### 3. 清除缓存或关闭缓存机制 在使用 MyBatis 等 ORM 框架时,可以通过清除缓存来确保查询操作获取最新数据。 ```java @Transactional public void updateAndQueryWithMyBatis() { myMapper.updateRecord(); ((SqlSession) AopContext.currentProxy()).clearCache(); // 清除一级缓存 Record record = myMapper.selectById(1); // 查询最新数据 } ``` 此外,可以临时关闭二级缓存,或在更新操作后手动刷新缓存,以确保数据一致性。 #### 4. 使用 JdbcTemplate 或 Spring 封装的 DAO 操作 如果直接使用原始的 JDBC 操作(如 C3P0 数据源、PreparedStatement),而未使用 Spring 提供的 `JdbcTemplate`,则事务管理可能失效,导致更新后查询不到最新数据。 ```java @Autowired private JdbcTemplate jdbcTemplate; @Transactional public void updateData() { jdbcTemplate.update("UPDATE table SET column = ? WHERE id = ?", value, id); } @Transactional public Data queryData() { return jdbcTemplate.queryForObject("SELECT * FROM table WHERE id = ?", Data.class, id); } ``` 使用 `JdbcTemplate` 可确保所有数据库操作都处于 Spring 事务管理之下,从而避免事务边界不清、连接不一致等问题[^4]。 #### 5. 显式提交事务或使用同步机制 在异步场景中,可以使用 `TransactionTemplate` 显式控制事务提交,或在主线程中等待异步线程完成后再继续执行。 ```java @Autowired private TransactionTemplate transactionTemplate; public void updateAndAsyncQuery() { transactionTemplate.execute(status -> { updateData(); // 更新数据 return null; }); new Thread(() -> { queryData(); // 异步查询,此时事务已提交 }).start(); } ``` 通过 `TransactionTemplate` 显式提交事务,确保异步查询操作在事务完成后执行,从而能够读取到最新数据[^3]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值