spingboot的读提交事务不生效


CREATE TABLE `users` (
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `sex` varchar(2) NOT NULL,
  `AGE` int(11) NOT NULL,
  `email` varchar(32) NOT NULL,
  `password` varchar(40) NOT NULL,
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_unique` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci


INSERT INTO `dba6`.`users`(`name`, `sex`, `AGE`, `email`, `password`, `id`) VALUES ('张三', '男', 11, '111@163.con', '112233', 1);
import com.example.mybatis_plus.entity.UsersEntity;
import com.example.mybatis_plus.mapper.UsersDao;
import com.example.mybatis_plus.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;


@Service("usersService")

public class UsersServiceImpl  implements UsersService {

    @Autowired
    private UsersDao usersDao;

    @Override
    @Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public void updateUser() throws InterruptedException {

            System.out.println("开始执行了");
            UsersEntity usersEntity=usersDao.selectById(1);

            usersEntity.setName("李四");
            usersDao.updateById(usersEntity);

            Thread.sleep(20*000);


    }

    @Override
    @Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public void getUser() throws InterruptedException {
        UsersEntity usersEntity = usersDao.selectById(1);
        System.out.println(usersEntity);

        Thread.sleep(60*1000);
        usersEntity = usersDao.selectById(1);
        System.out.println(usersEntity);
    }
}
  1. 我先执行updateUser,再执行getUser。
  2. 两者事务,都是读提交。
  3. getUser第一次执行时,输出的name为张三
  4. 但getUser第二次执行时,updateUser已经更新(20秒),而事务是读提交的,此时getUser在60秒后读取,应该是李四,但结果依旧为张三??把事务替换为可重复读,也是这个效果
UsersEntity(name=张三, sex=男, age=11, email=111@163.con, password=112233, id=1)
开始执行了
UsersEntity(name=张三, sex=男, age=11, email=111@163.con, password=112233, id=1)

缓存问题,数据库应该已经改成李四了
一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。

参考GPT和自己的思路:根据您提供的代码和数据库表结构,我分析您的问题可能是由于您的事务隔离级别为READ_COMMITTED,这意味着每个事务只能看到已经提交的数据。但是在您的代码中,您在updateUser方法中修改了id=1的用户数据并将事务挂起20秒钟,然后在getUser方法中,您立即尝试读取id=1的用户数据,但此时updateUser事务尚未提交,因此您的查询仍然看到未修改的数据。

如果您希望在getUser方法中看到updateUser方法所做的更改,您需要等待updateUser事务提交,然后才能在另一个事务中看到更改。如果您在updateUser方法中提交事务,则在getUser方法中立即查看更改。所以您可以尝试将updateUser方法中的事务提交。

如果您希望在getUser方法中看到未提交的更改,则可以将事务隔离级别设置为READ_UNCOMMITTED。但是,这可能会导致脏读(读取未提交的数据)和不可重复读(同一事务内两次读取的数据不一致),因此需要谨慎使用。

另外,我建议您使用Spring的@Service注解来注入服务,而不是使用字符串名称,如下所示:

@Service
public class UsersServiceImpl implements UsersService {
  ...
}

这个问题可能涉及到数据库事务的隔离级别和并发问题,需要进一步排查。以下是一个可能的修改方案,添加了对数据库事务的异常处理和日志记录,以及对线程休眠的异常处理。

@Service("usersService")
public class UsersServiceImpl implements UsersService {

    private static final Logger logger = LoggerFactory.getLogger(UsersServiceImpl.class);

    @Autowired
    private UsersDao usersDao;

    @Override
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public void updateUser() throws Exception {
        logger.info("updateUser: 开始执行了");
        UsersEntity usersEntity = usersDao.selectById(1);

        usersEntity.setName("李四");
        usersDao.updateById(usersEntity);

        Thread.sleep(20 * 1000);

        logger.info("updateUser: 执行完成");
    }

    @Override
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public void getUser() throws Exception {
        UsersEntity usersEntity = usersDao.selectById(1);
        logger.info("getUser第一次执行: {}", usersEntity);

        Thread.sleep(60 * 1000);
        usersEntity = usersDao.selectById(1);
        logger.info("getUser第二次执行: {}", usersEntity);
    }

}

在实际应用中,我们还需要考虑如何处理并发读写操作的情况,以及如何提高数据库事务的性能。

可能和缓存有关,你试试把你的getUser方法改成这样试试:

@Override
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws InterruptedException {
    UsersEntity usersEntity = usersDao.selectByIdForUpdate(1);
    System.out.println(usersEntity);

    Thread.sleep(60 * 1000);
    usersEntity = usersDao.selectByIdForUpdate(1);
    System.out.println(usersEntity);
}

然后在application.properties中添加以下配置以禁用查询缓存,这样每次查询都会从数据库中获取最新数据:

spring.jpa.properties.hibernate.cache.use_query_cache=false
spring.jpa.properties.hibernate.cache.use_second_level_cache=false

看看这样能不能解决

你在updateUser()方法中的休眠等待时间写错了,你写的是 Thread.sleep(20x000),变成等待0秒了,应该是Thread.sleep(20x1000),中间是星号,你改下,这里星号打出来有问题。你先把这个修改过来。

该回答引用ChatGPT

根据提供的代码和信息,似乎问题与事务使用的隔离级别有关。

在updateUser()和getUser()方法中,都使用了Isolation.READ_COMMITTED隔离级别,这意味着每个事务可以从其他事务读取已提交的数据,但可能导致不可重复读取。

当updateUser()将用户的名称更新为“李四”时,它提交事务并释放数据上的锁。但是,由于getUser()仍在运行并且已经在更新之前读取了数据,因此它可能在下次读取之前无法看到更新的值。

为了解决这个问题,可以使用更高的隔离级别,如Isolation.REPEATABLE_READ或Isolation.SERIALIZABLE,这些级别提供了更多的一致性保证,但可能影响并发性;或者修改代码,确保getUser()方法在updateUser()方法进行更新后重新读取数据。

例如,您可以将getUser()方法修改为以下内容:


@Override
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws InterruptedException {
    UsersEntity usersEntity = usersDao.selectById(1);
    System.out.println(usersEntity);

    Thread.sleep(60*1000);

    // 在休眠后重新读取数据
    usersEntity = usersDao.selectById(1);
    System.out.println(usersEntity);
}

通过在休眠后重新读取数据,getUser()可以看到名称字段的更新值。

你把查询的条件改一下,将第二次查询的范围增加,包含第一次查询的内容,然后进行比较。 置于是缓存问题还是事务提交模式未生效问题,可以逐点排查