事务隔离级别一共有4种,分别为:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)、SERIALIZABLE(串行化),并发随着等级的提高而降低,mysql默认隔离等级为REPEATABLE READ。
READ UNCOMMITTED会产生脏读、不可重复读、幻读。
READ COMMITTED解决了脏读,会产生不可重复读、幻读。
REPEATABLE READ解决了脏读、不可重复读,解决部分幻读问题。(默认)
SERIALIZABLE不会产生任何问题,事务没有并发,效率差。
首先是update事务
@Transactional(value = "mainDatasourceTransactionManager")
public int update(long userId,String name) {
logger.info("---------- update start ----------");
AdminUserEntity adminUserEntity = new AdminUserEntity();
adminUserEntity.setUserId(userId);
adminUserEntity.setName(name);
logger.info("set name = '{}'",name);
int i = adminUserMapper.updateById(adminUserEntity);
logger.info("update sleeping");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("---------- update sleep end ----------");
return i;
}
@RequestMapping(value = "/update/{userId}/{name}")
public int update(@PathVariable(name = "userId") Long userId,@PathVariable(name = "name") String name){
int i = adminUserService.update(userId,name);
return i;
}
select事务:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.READ_UNCOMMITTED)
public AdminUserEntity getDataUnCommit(long userId) {
AdminUserEntity adminUserEntity = adminUserMapper.selectById(userId);
logger.info("getData [READ_UNCOMMITTED] => adminUserEntity:{}",adminUserEntity);
return adminUserEntity;
}
@RequestMapping(value = "/getData/unCommit/{userId}")
public AdminUserEntity getDataUnCommit(@PathVariable(name = "userId") Long userId){
AdminUserEntity data = adminUserService.getDataUnCommit(userId);
return data;
}
更新后先让线程sleep 10秒钟,暂不提交update事务,然后我们在期间进行select查询,预期效果为可以查询到这个未提交的事务修改的数据。
启动项目,先访问http://localhost:8080/getData/unCommit/1
2018-11-13 10:07:30.106 INFO 324956 --- [nio-8080-exec-1] c.m.p.r.s.impl.AdminUserServiceImpl : getData [READ_UNCOMMITTED] => adminUserEntity:AdminUserEntity{userId=1, loginId='321312', name='java', phone='15251708673', roleId=null, localPassword='DBBE2055FF0198D265432967F2AC696E', passwordSuffix='null', birthday=null, sex='0001-3', token='null', enable=true}
再访问http://localhost:8080/update/1/python,再刷新http://localhost:8080/getData/unCommit/1
2018-11-13 10:10:05.063 INFO 324956 --- [nio-8080-exec-7] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update start ----------
2018-11-13 10:10:05.063 INFO 324956 --- [nio-8080-exec-7] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'python'
2018-11-13 10:10:05.312 INFO 324956 --- [nio-8080-exec-7] c.m.p.r.s.impl.AdminUserServiceImpl : update sleeping
2018-11-13 10:10:06.099 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : getData [READ_UNCOMMITTED] => adminUserEntity:AdminUserEntity{userId=1, loginId='321312', name='python', phone='15251708673', roleId=null, localPassword='DBBE2055FF0198D265432967F2AC696E', passwordSuffix='null', birthday=null, sex='0001-3', token='null', enable=true}
2018-11-13 10:10:15.313 INFO 324956 --- [nio-8080-exec-7] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update sleep end ----------
和预期的一样,在update事务未提交的时候,另一个事务就已经读到了这个新的数据。
保持update事务方法不变,新增查询方法:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.READ_COMMITTED)
public AdminUserEntity getDataCommitted() {
List
@RequestMapping(value = "/getData/committed")
public AdminUserEntity getDataCommit(){
AdminUserEntity data = adminUserService.getDataCommitted();
return data;
}
现在数据库中user_id为1的这条数据name是"python",然后我们访问http://localhost:8080/update/1/c++,update事务开启、线程睡眠,此时访问http://localhost:8080/getData/committed:
2018-11-13 10:19:30.247 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update start ----------
2018-11-13 10:19:30.247 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'c++'
2018-11-13 10:19:30.316 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : update sleeping
2018-11-13 10:19:32.110 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : getData [READ_COMMITTED] (1) :
2018-11-13 10:19:32.110 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=python, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
我们可以发现,未被提交的事务的数据未被读取,name还是为"python"。
然后这个线程也进入睡眠,当update事务结束,getData中的事务又进行了一次查询:
2018-11-13 10:19:40.318 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update sleep end ----------
2018-11-13 10:19:52.152 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : getData [READ_COMMITTED] (2) :
2018-11-13 10:19:52.152 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=c++, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
此时查到了刚刚已经提交的修改产生的新数据(name为"c++"),但是,出现了不可重复读的问题(同一个事务两次一样的查询出现了不同的结果)。
update事务方法不变,新增查询方法:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.REPEATABLE_READ)
public AdminUserEntity getDataRepeatable() {
List
@RequestMapping(value = "/getData/repeatable")
public AdminUserEntity getDataRepeatable(){
AdminUserEntity data = adminUserService.getDataRepeatable();
return data;
}
现在数据库中user_id为1的这条数据name是"c++",然后我们访问http://localhost:8080/update/1/c,update事务开启、线程睡眠,此时访问http://localhost:8080/getData/repeatable:
2018-11-13 10:29:31.925 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update start ----------
2018-11-13 10:29:31.925 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'c'
2018-11-13 10:29:31.997 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : update sleeping
2018-11-13 10:29:33.047 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (1) :
2018-11-13 10:29:33.047 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=c++, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
第一次查询到name为"c++",没有读取到未提交的数据。
2018-11-13 10:29:41.997 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update sleep end ----------
2018-11-13 10:29:53.087 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (2) :
2018-11-13 10:29:53.087 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=c++, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
当update事务提交,进行第二次查询,读取到的数据还是"c++",但是此时数据库中的name已经为"c"了。
调整顺序,先开启查询事务,再开启update事务,update事务结束,查询事务结束,两次查询结果也是一致的。
步骤:先访问http://localhost:8080/getData/repeatable,再访问http://localhost:8080/update/1/go,日志如下:
2018-11-13 10:34:02.569 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (1) :
2018-11-13 10:34:02.569 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=c, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
2018-11-13 10:34:03.764 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update start ----------
2018-11-13 10:34:03.764 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'go'
2018-11-13 10:34:03.842 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : update sleeping
2018-11-13 10:34:13.843 INFO 324956 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- update sleep end ----------
2018-11-13 10:34:22.603 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (2) :
2018-11-13 10:34:22.603 INFO 324956 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=c, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
数据库:
新增insert事务方法:
@Transactional(value = "mainDatasourceTransactionManager")
public long insert(String name) {
logger.info("---------- insert start ----------");
long id = System.currentTimeMillis();
logger.info("the id is : {}",id);
AdminUserEntity adminUserEntity = new AdminUserEntity();
adminUserEntity.setUserId(id);
adminUserEntity.setName(name);
adminUserEntity.setPhone("110");
logger.info("set name = '{}'",name);
int i = adminUserMapper.insert(adminUserEntity);
logger.info("insert sleeping");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("---------- insert sleep end ----------");
return id;
}
@RequestMapping(value = "/insert/{name}")
public long insert(@PathVariable(name = "name") String name){
long i = adminUserService.insert(name);
return i;
}
查询方法保持不变
先访问http://localhost:8080/getData/repeatable,再访问http://localhost:8080/insert/javascript,日志如下:
2018-11-13 10:39:26.410 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (1) :
2018-11-13 10:39:26.410 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=go, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
2018-11-13 10:39:27.638 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert start ----------
2018-11-13 10:39:27.638 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : the id is : 1542076767638
2018-11-13 10:39:27.638 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'javascript'
2018-11-13 10:39:27.713 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : insert sleeping
2018-11-13 10:39:37.713 INFO 324956 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert sleep end ----------
2018-11-13 10:39:46.447 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : getData [REPEATABLE_READ] (2) :
2018-11-13 10:39:46.448 INFO 324956 --- [nio-8080-exec-9] c.m.p.r.s.impl.AdminUserServiceImpl : {user_id=1, login_id=321312, name=go, phone=15251708673, local_password=DBBE2055FF0198D265432967F2AC696E, password_suffix=null, birthday=null, sex=0001-3, token=null, role_id=null, enable=true}
数据库:
同一个事务中进行两次查询,在两次查询期间产生完成了一个insert事务,两次查询结果都一样,并没有查询到javascript这条记录。
难道REPEATABLE READ已经解决了幻读问题?
保持insert方法不变,新增一个selectAndUpdate方法:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.REPEATABLE_READ)
public int selectAndUpdate(String name) {
logger.info("---------- selectAndUpdate start ----------");
AdminUserEntity adminUserEntity = new AdminUserEntity();
adminUserEntity.setName(name);
logger.info("set name = '{}'",name);
//1.查询全表数量
Integer size = adminUserMapper.selectCount(new QueryWrapper());
logger.info("size1 :{}",size);
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.全表更新 name字段
int updateSize = adminUserMapper.update(new AdminUserEntity(),new UpdateWrapper().lambda().set(AdminUserEntity::getName,name));
logger.info("selectAndUpdateSize :{}",updateSize);
//3.查询全表数量
List
@RequestMapping(value = "/selectAndUpdate/{name}")
public int selectAndUpdate(@PathVariable(name = "name") String name){
int i = adminUserService.update(name);
return i;
}
先访问http://localhost:8080/selectAndUpdate/nodeJs,再访问http://localhost:8080/insert/net,日志如下:
2018-11-13 10:51:05.544 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate start ----------
2018-11-13 10:51:05.544 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'nodeJs'
2018-11-13 10:51:05.806 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : size1 :2
2018-11-13 10:51:06.084 INFO 326080 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert start ----------
2018-11-13 10:51:06.084 INFO 326080 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : the id is : 1542077466084
2018-11-13 10:51:06.085 INFO 326080 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'net'
2018-11-13 10:51:06.171 INFO 326080 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : insert sleeping
2018-11-13 10:51:16.171 INFO 326080 --- [nio-8080-exec-6] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert sleep end ----------
2018-11-13 10:51:26.006 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : selectAndUpdateSize :3
2018-11-13 10:51:26.063 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : size2:3
2018-11-13 10:51:26.064 INFO 326080 --- [io-8080-exec-10] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate sleep end ----------
可以看到,第一次查询到的数据量为2,然后等待insert结束,selectAndUpdate方法进行全表update,更新到的行数为3,第二次查询出来的全表数量也为3。
如果我们把selectAndUpdate方法中的全表更新放在sleep之前,也就是说,先开启A事务、查询数量、全表更新name字段,B事务插入、b事务结束,A事务再次查询、A事务结束。
具体步骤如下:
insert方法不变,新增selectAndUpdate2方法:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.REPEATABLE_READ)
public int selectAndUpdate2(String name) {
logger.info("---------- selectAndUpdate2 start ----------");
AdminUserEntity adminUserEntity = new AdminUserEntity();
adminUserEntity.setName(name);
logger.info("set name = '{}'",name);
//1.查询全表数量
Integer size = adminUserMapper.selectCount(new QueryWrapper());
logger.info("size1 :{}",size);
//2.全表更新 name字段
int updateSize = adminUserMapper.update(new AdminUserEntity(),new UpdateWrapper().lambda().set(AdminUserEntity::getName,name));
logger.info("selectAndUpdateSize :{}",updateSize);
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.查询全表数量
List
@RequestMapping(value = "/selectAndUpdate2/{name}")
public int selectAndUpdate2(@PathVariable(name = "name") String name){
int i = adminUserService.selectAndUpdate2(name);
return i;
}
当前数据库一共3条记录,name均为"nodeJs",先访问http://localhost:8080/selectAndUpdate2/ruby,再访问http://localhost:8080/insert/e,日志如下:
2018-11-13 11:02:57.494 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate2 start ----------
2018-11-13 11:02:57.495 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'ruby'
2018-11-13 11:02:57.718 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : size1 :3
2018-11-13 11:02:57.822 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : selectAndUpdateSize :3
2018-11-13 11:02:58.022 INFO 326512 --- [nio-8080-exec-8] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert start ----------
2018-11-13 11:02:58.022 INFO 326512 --- [nio-8080-exec-8] c.m.p.r.s.impl.AdminUserServiceImpl : the id is : 1542078178022
2018-11-13 11:02:58.023 INFO 326512 --- [nio-8080-exec-8] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'e'
2018-11-13 11:03:17.868 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : size2:3
2018-11-13 11:03:17.868 INFO 326512 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate2 sleep end ----------
2018-11-13 11:03:17.991 INFO 326512 --- [nio-8080-exec-8] c.m.p.r.s.impl.AdminUserServiceImpl : insert sleeping
2018-11-13 11:03:27.991 INFO 326512 --- [nio-8080-exec-8] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert sleep end ----------
update事务只修改了3条数据,两次select查询到的数据量也为3,此时数据库中数据量为4:
题外话:这里要注意几个时间点,insert方法sleep 10秒钟,但是insert事务开启的时间为:11:02:58,结束的时间为:11:03:27,且在selectAndUpdate2事务结束之后10秒钟后才结束,selectAndUpdate2中update的时候全表锁定(因为没有加索引条件),insert方法需等待update的锁释放。
保持insert方法不变,新增selectAndUpdate3方法:
@Transactional(value = "mainDatasourceTransactionManager",isolation = Isolation.SERIALIZABLE)
public int selectAndUpdate3(String name) {
logger.info("---------- selectAndUpdate3 start ----------");
AdminUserEntity adminUserEntity = new AdminUserEntity();
adminUserEntity.setName(name);
logger.info("set name = '{}'",name);
//1.查询全表数量
Integer size = adminUserMapper.selectCount(new QueryWrapper());
logger.info("size1 :{}",size);
//2.全表更新 name字段
int updateSize = adminUserMapper.update(new AdminUserEntity(),new UpdateWrapper().lambda().set(AdminUserEntity::getName,name));
logger.info("selectAndUpdateSize :{}",updateSize);
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.查询全表数量
List
@RequestMapping(value = "/selectAndUpdate3/{name}")
public int selectAndUpdate3(@PathVariable(name = "name") String name){
int i = adminUserService.selectAndUpdate3(name);
return i;
}
先清空下数据库,只剩下一条user_id = 1,name = "ruby"的记录。
访问:http://localhost:8080/selectAndUpdate3/vb,再访问:http://localhost:8080/insert/kotlin,日志如下:
2018-11-13 14:38:12.332 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate3 start ----------
2018-11-13 14:38:12.332 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'vb'
2018-11-13 14:38:12.547 INFO 328996 --- [nio-8080-exec-4] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert start ----------
2018-11-13 14:38:12.547 INFO 328996 --- [nio-8080-exec-4] c.m.p.r.s.impl.AdminUserServiceImpl : the id is : 1542091092547
2018-11-13 14:38:12.547 INFO 328996 --- [nio-8080-exec-4] c.m.p.r.s.impl.AdminUserServiceImpl : set name = 'kotlin'
2018-11-13 14:38:12.727 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : size1 :1
2018-11-13 14:38:13.019 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : selectAndUpdateSize :1
2018-11-13 14:38:33.114 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : size2:1
2018-11-13 14:38:33.114 INFO 328996 --- [nio-8080-exec-3] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- selectAndUpdate3 sleep end ----------
2018-11-13 14:38:33.172 INFO 328996 --- [nio-8080-exec-4] c.m.p.r.s.impl.AdminUserServiceImpl : insert sleeping
2018-11-13 14:38:43.173 INFO 328996 --- [nio-8080-exec-4] c.m.p.r.s.impl.AdminUserServiceImpl : ---------- insert sleep end ----------
数据库:
时间点:14:38:12和14:38:43为insert事务的开始和结束,且结束时间为selectAndUpdate3结束后的10秒;
selectAndUpdate3方法查询到的数据始终是一样的,并且update没有覆盖掉name = 'kotlin'这条记录,由此可见,事务这里已经串行化了。
RU在无锁环境下进行,RR是快照读,读到的是历史数据,所以能保证数据一致性,但是在update的时候会发生幻读。