先给个答案:
在可重复读(REPEATABLE READ)隔离级别下,当前事务是可以读取到当前事务修改的数据结果,这是通过 Read View 机制实现的。
Read View 记录了事务开始时数据库的快照状态,并且在当前事务内部,事务的修改操作(INSERT、UPDATE、DELETE)会在事务的私有临时空间中存储修改的结果。
当当前事务内进行数据读取操作时,MySQL会先检查数据的版本,包括 DATA_TRX_ID(最后一次修改该数据的事务ID)和 ROLL_PTR(回滚指针)等。如果当前事务修改了数据,并且事务ID和回滚指针与当前事务的 Read View 兼容,那么当前事务可以读取到自己修改后的数据结果。
因此,在可重复读隔离级别下,当前事务内的读操作可以看到当前事务修改的数据结果,因为数据的版本信息与当前事务的 Read View 是一致的。
需要注意的是,其他并发事务在默认的可重复读隔离级别下仍然无法看到当前事务的更新结果,直到当前事务提交后才对其他事务可见。只有在当前事务内部才可以读取到自己修改的数据结果。
MVCC参考链接:http://www.itabin.com/mysql-isolation3/
上试验
试验环境:Mysql8,SpringBoot
1、首先获取mysql的默认隔离级别
SELECT @@GLOBAL.transaction_isolation AS '默认隔离级别';
结果
2、模拟两个线程并发操作
表结构及数据
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`img` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`signature` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 初始化数据 注意看img字段
INSERT INTO `Testdb`.`user` (`id`, `username`, `password`, `img`, `signature`) VALUES (1, '1', NULL, '1', '1');
线程1,线程1睡一下,模拟线程1未执行完成整个事务时,线程2并发
SELECT @@GLOBAL.transaction_isolation AS '默认隔离级别'; -- 结果1
START TRANSACTION;
SELECT * FROM user WHERE id = 1 ; -- 结果2
UPDATE user SET img = '/img' WHERE id = 1;
SELECT SLEEP(10); -- 结果3
SELECT * FROM user WHERE id = 1 ; -- 结果4
COMMIT;
线程2
SELECT @@GLOBAL.transaction_isolation AS '默认隔离级别'; -- 结果1
START TRANSACTION;
SELECT * FROM user WHERE id = 1 ; -- 结果2
COMMIT;
开始执行线程1线程2sql
线程1执行结果
线程2执行结果 ,由于线程2未睡眠,所以读到的还是img=`1`的数据,也就是事务2自己的ReadView
所以,RR级别下,事务2读取到的还是之前已经提交的读取。之后事务1睡眠完成后,读取到的也是自己当前事务内修改的数据img='/img',而不会影响事务2的读取(或其他事务),其实就是解决了不可重复读问题。
为了观察RR级别下,事务开启时的查询ReadView整个事务共用一个ReadView,可以将线程2也加个睡眠时间重新开始运行一次,你会发现的确共用了一个ReadView
SELECT @@GLOBAL.transaction_isolation AS '默认隔离级别'; -- 结果1
START TRANSACTION;
SELECT * FROM user WHERE id = 1 ; -- 结果2
SELECT SLEEP(10); -- 结果3
SELECT * FROM user WHERE id = 1 ; -- 结果4
COMMIT;
输出结果3的时候,线程1已经操作完成了img字段变换为'/img',但是线程2睡眠的10秒后读取到的img='1'。由此可看出当前线程2事务共用了一个ReadView
可以参考这篇文章详细理解下
参考链接:http://www.itabin.com/mysql-isolation3/
Java代码实现
@Service
public class AsyncService {
@Autowired
private UserService userService;
public void test() {
MyThread myThread = new MyThread(userService);
MyThread myThread2 = new MyThread(userService);
myThread2.start();
myThread.start();
}
}
class MyThread extends Thread {
private final UserService userService;
public MyThread(UserService userService) {
this.userService = userService;
}
@Override
public void run() {
try {
userService.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Service
@Slf4j
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)
public void test() throws InterruptedException {
User user = userMapper.selectByPrimaryKey(1);
log.info(JSON.toJSONString(user));
user.setImg("/img");
userMapper.updateByPrimaryKey(user);
Thread.sleep(10);
User user2 = userMapper.selectByIdUser(1);
log.info("user2{}",JSON.toJSONString(user2));
}
}
isolation 不写的情况下默认使用的数据库的隔离级别
执行结果如下
2023-06-19 12:10:33.056 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper selectByPrimaryKey
2023-06-19 12:10:33.089 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : UserMapper selectByPrimaryKey use time 0.033s
2023-06-19 12:10:33.120 INFO 8948 --- [ Thread-9] c.t.mybatisprotest.service.UserService : {"id":1,"img":"1","signature":"1","username":"1"}
2023-06-19 12:10:33.120 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper updateByPrimaryKey
2023-06-19 12:10:33.143 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper selectByPrimaryKey
2023-06-19 12:10:33.143 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : UserMapper updateByPrimaryKey use time 0.023s
2023-06-19 12:10:33.153 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper selectByIdUser
2023-06-19 12:10:33.155 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : UserMapper selectByPrimaryKey use time 0.012s
2023-06-19 12:10:33.155 INFO 8948 --- [ Thread-10] c.t.mybatisprotest.service.UserService : {"id":1,"img":"1","signature":"1","username":"1"}
2023-06-19 12:10:33.155 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper updateByPrimaryKey
2023-06-19 12:10:33.165 INFO 8948 --- [ Thread-9] c.test.mybatisprotest.aspect.UserAspect : UserMapper selectByIdUser use time 0.012s
2023-06-19 12:10:33.166 INFO 8948 --- [ Thread-9] c.t.mybatisprotest.service.UserService : user2{"id":1,"img":"/img","signature":"1","username":"1"}
2023-06-19 12:10:33.182 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : UserMapper updateByPrimaryKey use time 0.027s
2023-06-19 12:10:33.194 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : Start running UserMapper selectByIdUser
2023-06-19 12:10:33.205 INFO 8948 --- [ Thread-10] c.test.mybatisprotest.aspect.UserAspect : UserMapper selectByIdUser use time 0.011s
2023-06-19 12:10:33.206 INFO 8948 --- [ Thread-10] c.t.mybatisprotest.service.UserService : user2{"id":1,"img":"/img","signature":"1","username":"1"}