不知道大家有没有想过数据库的事务隔离级别和@Transaction设置的隔离级别到底是什么关系?

数据库设置的高隔离级别,@Transaction设置低隔离级别,那么事务的隔离级别到底以谁的为主?

下面就让我们一起去用代码研究一下

首先我们mysql数据库的默认隔离级别是read-commit读已经提交;

一、READ_UNCOMMITTED 读未提交

我们知道,这种隔离级别会产生脏读;

那么我们先测试@Transactional(isolation = Isolation.READ_UNCOMMITTED) 读未提交;

先上测试代码

@Service
public class InfoService{@Autowiredprivate Mapper mapper;//测试事务的隔离级别  读未提交@Transactional(isolation = Isolation.READ_UNCOMMITTED)public void getInfo() {//传入id查询数据while (true) {Info info = mapper.selectInfo("jzcs123");if (info != null) {System.out.println(Thread.currentThread().getName()+"获取到值");return;}else {//等2s继续查询Thread.sleep(2000);}}}//新增数据的方法@Transactionalpublic void addInfo() {//添加数据mapper.addInfo("jzcs123");System.out.println(Thread.currentThread().getName()+"已经插入数据了,但是事务没有提交");//睡眠一分钟后事务才会提交Thread.sleep(60 * 1000);System.out.println(Thread.currentThread().getName()+"事务已经提交了");}}

上面写了两个方法,都是有事务的,一个查询方法的事务隔离级别是读未提交,另外一个插入的方法就默认事务;

public class TestService {@Autowiredprivate InfoService infoService;public static void main(String[] args) {//开启不同的线程去操作ExecutorService executorService = Executors.newFixedThreadPool(3);//第一个线程调用查询方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.getInfo();}});//第二个线程调用插入方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.addInfo();}});//主线程睡眠2s,再开启第三个线程Thread.sleep(2000);//第三个线程调用查询方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.getInfo();}});}
}

上面代码是开启三个线程,第一个线程调用查询方法,第二个线程调用插入方法,第三个线程调用查询方法;

我们来看结果:

pool-13-thread-1没有获取到值
pool-13-thread-2已经插入数据了,但是事务没有提交
pool-13-thread-1没有获取到值
pool-13-thread-3获取到值
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
...
pool-13-thread-2事务已经提交了
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值

pool-13-thread-3 在pool-13-thread-2提交事务前,就直接查询出来了值,符合读未提交的隔离级别,也就发生了脏读;

是不是感觉有点奇怪,pool-13-thread-1全程没有获取到值,这是为什么呢?

因为mybatis有一级缓存,即在同一个事务中,sql相同,参数也相同,那么获取到的结果也是相同的,即使其他事务已经把该数据更改,该事务获取到的值仍然不会变;pool-13-thread-1在pool-13-thread-2插入数据之前访问了数据库,查询结果为空,也就把结果保存到了一级缓存,未获取到值,事务结束,所以也就一直不会得到结果了;

二、读已提交 READ_COMMITTED

我们知道这种隔离级别不会产生脏读,但是会产生不可重复读的问题

接下来测试@Transactional(isolation = Isolation.READ_COMMITTED) 读已提交

@Service
public class InfoService{@Autowiredprivate Mapper mapper;//测试事务的隔离级别  读已提交@Transactional(isolation = Isolation.READ_COMMITTED)public void getInfo() {//传入id查询数据while (true) {Info info = mapper.selectInfo("jzcs123");if (info != null) {System.out.println(Thread.currentThread().getName()+"获取到值");return;}else {//等2s继续查询Thread.sleep(2000);}}}//新增数据的方法@Transactionalpublic void addInfo() {//添加数据mapper.addInfo("jzcs123");System.out.println(Thread.currentThread().getName()+"已经插入数据了,但是事务没有提交");//不需要睡眠,直接结束方法,提交事务System.out.println(Thread.currentThread().getName()+"事务已经提交了");}}

上面写了两个方法,都是有事务的,一个查询方法的事务隔离级别是读已提交,另外一个插入的方法就默认事务;

public class TestService {@Autowiredprivate InfoService infoService;public static void main(String[] args) {//开启不同的线程去操作ExecutorService executorService = Executors.newFixedThreadPool(2);//第一个线程调用插入方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.addInfo();}});//主线程睡眠2s,再开启第二个线程Thread.sleep(2000);//第二个线程调用查询方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.getInfo();}});}
}

上面代码是开启两个线程,第二个线程调用插入方法,第二个线程调用查询方法;为什么我们插入方法不需要睡眠直接提交数据,因为mybatis的一级缓存,如果我们查询方法先执行,肯定查询未空,那么后续查询都会走一级缓存,也就始终查询不到数据,也就没有验证的意义了;

我们来看结果:

pool-13-thread-1已经插入数据了,但是事务没有提交
pool-13-thread-1事务已经提交了
pool-13-thread-2获取到值

显然查询的线程获取到插入线程提交了的数据;

三、重复读 REPEATABLE_READ

这种还没有好的方法去测试,因为一级缓存,不好测试;

四、序列化 SERIALIZABLE

我们知道,隔离级别设置成串行化是不会出现脏读,不可重复的,幻读这些问题的;

接下来测试@Transactional(isolation = Isolation.SERIALIZABLE) 串行化

@Service
public class InfoService{@Autowiredprivate Mapper mapper;//测试事务的隔离级别  序列化@Transactional(isolation = Isolation.SERIALIZABLE)public void getInfo() {//传入id查询数据while (true) {Info info = mapper.selectInfo("jzcs123");if (info != null) {System.out.println(Thread.currentThread().getName()+"获取到值");return;}else {//等2s继续查询Thread.sleep(2000);}}}//新增数据的方法@Transactionalpublic void addInfo() {//添加数据mapper.addInfo("jzcs123");System.out.println(Thread.currentThread().getName()+"已经插入数据了,但是事务没有提交");//不需要睡眠,直接结束方法,提交事务System.out.println(Thread.currentThread().getName()+"事务已经提交了");}}

上面写了两个方法,都是有事务的,一个查询方法的事务隔离级别是串行化,另外一个插入的方法就默认事务;

public class TestService {@Autowiredprivate InfoService infoService;public static void main(String[] args) {//开启不同的线程去操作ExecutorService executorService = Executors.newFixedThreadPool(2);//第一个线程调用查询方法,隔离级别是串行化executorService.execute(new Runnable() {@Overridepublic void run() {infoService.getInfo();}});//主线程睡眠2s,再开启第二个线程Thread.sleep(2000);//第二个线程调用插入方法executorService.execute(new Runnable() {@Overridepublic void run() {infoService.addInfo();}});}
}

上面代码是开启两个线程,第一个线程调用查询方法,第二个线程调用插入方法;

我们看结果:

pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值
...
Exception in thread "pool-13-thread-2" org.springframework.dao.CannotAcquireLockException:
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may involve mapper.InfoMapper.insertSelective-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO info  ( id ) VALUES( ? )
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
pool-13-thread-1没有获取到值
pool-13-thread-1没有获取到值

看到没,查询的线程一直查不到数据,插入的线程因为等待时间过长,报错;这是为什么呢?

查询线程访问的方法的事务隔离级别是SERIALIZABLE串行化,该隔离级别会把该方法所涉及到的表全部锁起来,共享锁,其他事务只能查,不能写;所以,我们的另一个线程插入数据的方法,一直无法执行,查询方法事务一直没有结束,所以导致插入数据的线程等待超时报错;

总结:

其实事务的隔离级别网络上已经有很多说明和解析了,该篇文章旨在搞清楚声明式事务和数据库本身的事务隔离级别的关系;显然,如果我们使用@Transactional,不指定隔离级别,就会使用数据库的默认隔离级别,如果制定了,就会覆盖数据库的隔离级别;所以,每个事务的隔离级别是可以不一样的,因为其原子性;mybatis的一级缓存,其实就已经帮我们实现了可重复读的隔离级别;

@Transactional的四种隔离级别测试相关推荐

  1. MySQL(六)事物(ADID,四种隔离级别)(七)索引(索引测试,原则)

    6-事物 文章目录 6-事物 1,执行事物 2,模拟事务 3,事务的四种隔离级别 7-索引 7.1, 索引的分类 7.2,测试索引 插入100万数据 测试查询耗时 7.3,索引原则 事务原则:ACID ...

  2. 这一篇让你真正理解Mysql的四种隔离级别

    什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事务的结束有 ...

  3. 温故而知新:MySQL 四种隔离级别,你还对答如流吗?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | 游泳的石头 来源 | https://www.jianshu.com/p/8d73 ...

  4. 面试问烂的 MySQL 四种隔离级别,看完吊打面试官!

    作者:游泳的石头 https://www.jianshu.com/p/8d735db9c2c0 什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被 ...

  5. 真正理解 MySQL 的四种隔离级别

    什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事务的结束有 ...

  6. mysql什么隔离级别最好_面试必问的 Mysql 四种隔离级别,看完吊打面试官

    什么是事务 事务是应用程序中一系列严密的操做,全部操做必须成功完成,不然在每一个操做中所做的全部更改都会被撤消.也就是事务具备原子性,一个事务中的一系列的操做要么所有成功,要么一个都不作.sql 事务 ...

  7. mysql 秒杀 隔离级别_MySQL 四种隔离级别详解,看完吊打面试官

    什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事务的结束有 ...

  8. mysql选择哪个隔离级别更好_深入理解Mysql的四种隔离级别

    一.首先什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事 ...

  9. MySQL常见的四种隔离级别!

    目录 1.什么是事务 2.事务的 ACID 3.Mysql的四种隔离级别 4.测试Mysql的隔离级别 1.什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所 ...

最新文章

  1. (二)、MariaDB、Apache软件安装
  2. SQL DISTINCT 多字段查询用法
  3. MATLAB上的GPU加速计算——学习笔记
  4. 别人家只会编段子,谷歌带大家找乐子 | 愚人节の真 · 大型线下踏春游戏
  5. Bzoj2124(p5364): 等差子序列
  6. JD_Source Code for problem 1581
  7. 运行JProfiler:ERROR: Invalid license key. Aborting
  8. 海康威视球机摄像头晚上补光灯闪烁问题
  9. Java分页详细步骤
  10. Word:转换PDF
  11. 兄弟打印机打印时显示服务器内部错误,打印机出现内部错误无法打印什么原因...
  12. java实现9*9乘法表
  13. 从阿里离职10天后,疯狂投简历面了4家公司,我的感受——不可名状的痛
  14. 干货 | 在搜索引擎广告关键词生成上,算法可以做什么?
  15. 西文是指什么,【英语中的复合句是指什么具体来说是指哪些类的语法知识?】作业帮...
  16. 小程序里说的冷启动和热启动是什么
  17. 华为交换机端口配置流量统计
  18. 玩转NVIDIA Jetson AGX Xavier--- 中文用户手册
  19. 电脑故障(一)台式电脑看网络直播屏幕黑屏
  20. Linux下批量修改文件名(rename)

热门文章

  1. 面对不同种类的处理结果——result type=
  2. 关于在动力气象学中遇到了几种坐标系
  3. SQL Server 2008 (2008 R2) 清理日志方法
  4. 零基础自学平面设计需要学些什么?
  5. PotPlayer老是提醒更新,怎么禁止PotPlayer联网?
  6. 行车路线(CCF201712-4)
  7. 命名实体识别的一点经验与技巧
  8. Python文本分析(NLTK,jieba,snownlp)
  9. android PowerManager goToSleep 等用法
  10. VideoStream流媒体(VOD视频点播)系统平台