@Transactional注解属性(3)


文章目录

1、问题

2、解决方案


1、问题

同一个应用程序中的多个事务或不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题,这些问题可分为如下三种类型:

脏读(Drity Read):已知有两个事务A和B, A读取了已经被B更新但还没有被提交的数据,之后,B回滚事务,A读取的数据就是脏数据。

场景:公司发工资了,领导把5000元打到Tom的账号上,但是该事务并未提交,而Tom正好去查看账户,发现工资已经到账,账户多了5000元,非常高兴,可是不幸的是,领导发现发给Tom的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,Tom再次查看账户时发现账户只多了2000元,Tom空欢喜一场,从此郁郁寡欢,走上了不归路…...

分析:上述情况即为脏读,两个并发的事务:“事务B:领导给Tom发工资”、“事务A:Tom查询工资账户”,事务A读取了事务B尚未提交的数据。

不可重复读(Non-repeatable read);已知有两个事务A和B,A 多次读取同一数据,B 在A多次读取的过程中对数据作了修改并提交,导致A多次读取同一数据时,结果不一致,

场景:Tom拿着工资卡去消费,酒足饭饱后在收银台买单,服务员告诉他本次消费1000元,Tom将银行卡给服务员,服务员将银行卡插入POS机,POS机读到卡里余额为3000元,就在Tom磨磨蹭蹭输入密码时,他老婆以迅雷不及掩耳盗铃之势把Tom工资卡的3000元转到自己账户并提交了事务,当Tom输完密码并点击“确认”按钮后,POS机检查到Tom的工资卡已经没有钱,扣款失败,Tom十分纳闷,明明卡里有钱,于是怀疑POS有鬼,和收银小姐姐大打出手,300回合之后终因伤势过重而与世长辞,Tom老婆痛不欲生,郁郁寡欢,从此走上了不归路......
分析:上述情况即为不可重复读,两个并发的事务,“事务A:POS机扣款”、“事务B:Tom的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新数据并提交了事务,而事务A再次读取该数据扣款时,数据已经发生了改变。

幻读(Phantom Read)::已知有两个事务A和B,A从一个表中读取了数据,然后B在该表中插入了一些新数据,导致A再次读取同一个表, 就会多出几行,简单地说,一个事务中先后读取一个范围的记录,但每次读取的纪录数不同,称之为幻象读

场景:Tom的老婆工作在银行部门,她时常通过银行内部系统查看Tom的工资卡消费记录。2019年5月的某一天,她查询到Tom当月工资卡的总消费额(select sum(amount) from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05')为80元,Tom的老婆非常吃惊,心想“老公真是太节俭了,嫁给他真好!”,而Tom此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录并提交了事务,沉浸在幸福中的老婆查询了Tom当月工资卡消费明细(select amount from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05')一探究竟,可查出的结果竟然发现有一笔1000元的消费,Tom的老婆瞬间怒气冲天,外卖订购了一个大号的榴莲,傍晚降临,Tom生活在了水深火热之中,只感到膝盖针扎的痛...... 
分析:上述情况即为幻读,两个并发的事务,“事务A:获取事务B消费记录”、“事务B:添加了新的消费记录”,事务A获取事务B消费记录时数据多出了一条。


2、解决方案

①根据实际需求,通过设置数据库的事务隔离级别可以解决多个事务并发情况下出现的脏读、不可重复读和幻读问题,数据库事务隔离级别由低到高依次为Read uncommitted、Read committed、Repeatable read和Serializable等四种。

a、Read uncommitted(读未提交):可能出现脏读、不可重复读和幻读。

b、Read committed(读提交):可以避免脏读,但可能出现不可重复读和幻读。大多数数据库默认级别就是Read committed,比如Sql Server数据库和Oracle数据库。注意:该隔离级别在写数据时只会锁住相应的行。

c、Repeatable read(重复读):可以避免脏读和不可重复读,但可能出现幻读。注意:事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

d、Serializable(序列化):可以避免脏读、不可重复读和幻读,但是并发性极低,一般很少使用。注意:该隔离级别在读写数据时会锁住整张表。

②数据库不同,其支持的事务隔离级别亦不相同:MySQL数据库支持上面四种事务隔离级别,默认为Repeatable read;Oracle 数据库支持Read committed和Serializable两种事务隔离级别,默认为Read committed。

MySQL事务隔离级别

①查看:MySQL数据库支持Read uncommitted、Read committed、Repeatable read和Serializable四种事务隔离级别,默认为Repeatable read,可以通过如下语句查看MySQL数据库事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;

结果如下所示

修改:MySQL数据库事务隔离级别的修改分为全局修改和当前session修改,具体修改方法如下:

Ⅰ、全局修改

a、在my.ini配置文件最后加上如下配置:

b、重启MySQL服务

Ⅱ、当前session修改,登录MySQL数据库后执行如下命令:

set session transaction isolation level read uncommitted;


③如何重启MySQL服务?

step1:找到这台电脑,鼠标右键,点击管理,出现如下所示图片

step2:点击上图中的服务,出现如下所示图片

step3:找到Mysql,鼠标右键,点击重新启动,则Mysql可以重新启动


④举例说明

a、Read uncommitted

Employye中的代码:

import java.sql.*;public class Employye {public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");statement = connection.createStatement();String sql = "select balance from account where card_id='6226090219290000'";resultSet = statement.executeQuery(sql);if(resultSet.next()) {System.out.println(resultSet.getDouble("balance"));}} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

boss中的代码:

import java.sql.*;public class Boss {public static void main(String[] args) {Connection connection = null;Statement statement = null;try {Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");connection.setAutoCommit(false);statement = connection.createStatement();String sql = "update account set balance=balance+5000 where card_id='6226090219290000'";statement.executeUpdate(sql);Thread.sleep(30000);//30秒后发现工资发错了,回滚事务,30秒内运行Employye中的代码,出现脏读现象connection.rollback();sql = "update account set balance=balance+2000 where card_id='6226090219290000'";statement.executeUpdate(sql);connection.commit();} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

首先,在my.ini配置文件最后加上如下配置:

然后,重启MySQL服务

其次,运行代码

先运行boss里面的代码,由于Thread.sleep(30000);使得boss表的事务未提交,在此期间如果运行Employye中的代码,出现如下结果

等boss里面的代码执行完,即事务回滚后,并提交,再执行Employye中的代码,出现如下结果,即数据发生改变

由以上结果可知, Read uncommitted是读未提交,可能出现脏读、不可重复读和幻读,上例中6000是未提交之前的数据,即脏数据,因此职员读到的6000为脏数据

b、Read committed(读提交)

Machine中代码

import java.sql.*;public class Machine {public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {double sum=1000;//消费金额Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");connection.setAutoCommit(false);statement = connection.createStatement();String sql = "select balance from account where card_id='6226090219290000'";resultSet = statement.executeQuery(sql);if(resultSet.next()) {System.out.println("余额:"+resultSet.getDouble("balance"));}System.out.println("请输入支付密码:");Thread.sleep(30000);//30秒后密码输入成功resultSet = statement.executeQuery(sql);if(resultSet.next()) {double balance = resultSet.getDouble("balance");System.out.println("余额:"+balance);if(balance<sum) {System.out.println("余额不足,扣款失败!");return;}}sql = "update account set balance=balance-"+sum+" where card_id='6226090219290000'";statement.executeUpdate(sql);connection.commit();System.out.println("扣款成功!");} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

Wife中代码

import java.sql.*;public class Wife {public static void main(String[] args) {Connection connection = null;Statement statement = null;try {double money=3000;//转账金额Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");connection.setAutoCommit(false);statement = connection.createStatement();String sql = "update account set balance=balance-"+money+" where card_id='6226090219290000'";statement.executeUpdate(sql);sql = "update account set balance=balance+"+money+" where card_id='6226090219299999'";statement.executeUpdate(sql);connection.commit();System.out.println("转账成功");} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

首先,在my.ini配置文件最后加上如下配置:

然后,重启MySQL服务

其次,运行代码

先运行Machine里面的代码,由于Thread.sleep(30000);使得Machine的事务未提交,在此期间如果运行Wife中的代码,银行卡里的钱被转走,即妻子对数据进行了修改并提交,会出现不重复读,此时妻子转账成功,如下图所示

之后观察Machine代码执行结果,出现如下结果,Machine不能使数据锁死,即不可重复读:

由以上结果可知, Read committed是读提交,可能出现不可重复读和幻读,避免脏读

c、Repeatable read(重复读)

首先,在my.ini配置文件最后加上如下配置:

然后,重启MySQL服务

其次,运行代码

先运行Machine里面的代码,由于Thread.sleep(30000);使得Machine的事务未提交,在此期间如果运行Wife中的代码,银行卡里的钱被转走,即妻子对数据进行了修改并提交,会出现不重复读,此时妻子转账成功,如下图所示

之后观察Machine代码执行结果,出现如下结果,Machine数据锁死,即可重复读:

由以上结果可知, Repeatable read是读提交,可以避免脏读和不可重复读,但可能出现幻读

注意:上述结果是红框中的3000是快照产生的,而实际的账户余额在妻子转账后已经变为0,程序执行完毕,Tom工资卡余额为会减掉1000元,即变为-1000,如下图所示:

d、幻读

Bank中的代码

import java.sql.*;public class Bank {public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");connection.setAutoCommit(false);statement = connection.createStatement();String sql = "select sum(amount) total from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";resultSet = statement.executeQuery(sql);if(resultSet.next()) {System.out.println("总额:"+resultSet.getDouble("total"));}Thread.sleep(30000);//30秒后查询2019年5月消费明细sql="select amount from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";resultSet = statement.executeQuery(sql);System.out.println("消费明细:");while(resultSet.next()) {double amount = resultSet.getDouble("amount");System.out.println(amount);}connection.commit();} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

Husband中的代码

import java.sql.*;public class Husband {public static void main(String[] args) {Connection connection = null;Statement statement = null;try {double sum=1000;//消费金额Class.forName("com.mysql.jdbc.Driver");String url = "jdbc:mysql://127.0.0.1:3306/test";connection = DriverManager.getConnection(url, "root", "root");connection.setAutoCommit(false);statement = connection.createStatement();String sql = "update account set balance=balance-"+sum+" where card_id='6226090219290000'";statement.executeUpdate(sql);sql = "insert into record (id,card_id,amount,create_time) values (3,'6226090219290000',"+sum+",'2019-05-19');";statement.executeUpdate(sql);connection.commit();} catch (Exception e) {e.printStackTrace();} finally {//释放资源}}
}

在上一个Mysql配置的基础上,丈夫消费插入数据,会使妻子通过银行读到的数据每次都不同,即幻读,结果如下

先运行Bank中的代码,在运行Husband中的代码,之后查看Bank的结果如下:

当第二次运行Bank中的代码时,结果如下,两次结果不一致,数据的插入导致了幻读

e、Serializable(序列化):可以避免脏读、不可重复读和幻读,但是并发性极低,一般很少使用。注意:该隔离级别在读写数据时会锁住整张表。

在my.ini配置文件最后配置序列化:

重启MySQL服务

运行以上Machine中的代码,再运行Wife中的代码,代码和以上的代码相同,会发现运行时要先执行完Machine中的代码,再执行Wife中的代码,即避免了不可重复读,幻读以及脏读,但并非性抵,一般不使用

如何在Spring中配置事务隔离机制,使用@Transactional注解属性——isolation,如下图所示

通过配置参数来在Sping中配置事务隔离机制,这种修改只对当前事务起作用,不是全局的

@Transactional注解属性(3)相关推荐

  1. 一口气说出 6 种 @Transactional 注解的失效场景

    一.事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚等操作,代码侵入性比较强, ...

  2. @Transactional注解在什么情况下失效?

    引言 1.@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功.要么同时失败. 2.使用@Transactional注解时需要注意许 ...

  3. @getmapping注解的作用_一口气说出6种,@Transactional注解的失效场景

    作者:程序员内点事 引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败.所以今天简单的和大家分享一下@Transactiona ...

  4. @Transactional 注解的失效场景

    作者:码农开花 链接:https://zhuanlan.zhihu.com/p/351260443 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 引言 有人咨询了一 ...

  5. 面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景

    一.Spring事务管理的两种方式 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚 ...

  6. spring声明式事务管理方式( 基于tx和aop名字空间的xml配置+@Transactional注解)

    1. 声明式事务管理分类 声明式事务管理也有两种常用的方式, 一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解. 显然基于注解的方式更简单易用,更清爽. ...

  7. 一口气说出 6种 @Transactional 注解失效场景

    引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败.所以今天简单的和大家分享一下@Transactional相关的知识. @T ...

  8. @getmapping注解的作用_@Transactional注解失效了?你遇到的是这6种场景吧!

    引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败.所以今天简单的和大家分享一下@Transactional相关的知识. @T ...

  9. 聊一聊Spring中@Transactional注解及其失效的七种场景

    文章目录 一.事务(基于AOP) 二.@Transactional介绍 三.@Transactional失效场景 说明:当我准备写我知道的那几个场景时,我发现有人比我写的更好,关键是好得多,于是我就用 ...

  10. 常见的 @Transactional 注解,你确定用对了吗?

    @Transactional属性详解 声明式事务管理建立在AOP之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 简而 ...

最新文章

  1. matplotlib pcolormech 用法
  2. Linux Shell编程 test命令
  3. [翻译练习] #selector() 和响应者链
  4. 【网络编程】之十、重叠IO Overlapped IO
  5. zClock - 置顶时钟, 倒计时, 网速显示
  6. python抽取指定url页面的title_Python使用scrapy爬虫,爬取今日头条首页推荐新闻
  7. 斯坦福 CS183f YC 创业课 2017 资料整理
  8. UI设计师缺乏灵感,看看这些可以临摹的网站架构!
  9. SQL Server:专业的DateTime范围
  10. cookie——登录注册极简版
  11. apache 搭建PHP多站点
  12. oracle dbms 存放位置,系统统计信息的保存位置
  13. 没事学学docker(二):本地Centos7和阿里云服务器安装docker及其解决安装出现的问题
  14. uni-app image组件当显示不出图片时显示默认图片
  15. ios healthkit_如何使用Swift从iOS的HealthKit中读写Mindful Minutes
  16. Win10安装Deepin双系统找不到启动项
  17. VGG16系列IV: 参数计算
  18. java excel 插入新行_复制一行,在多个工作表Excel VBA上插入多个带有粘贴信息的新行...
  19. 2020年的科技趋势:随时准备改变企业的未来!
  20. 大数据、云计算、物联网、数据库、数据仓库、OLAP、OLTP等学习大数据你必须了解的概念,我的学习总结

热门文章

  1. Web前端开发神器--WebStorm(JavaScript 开发工具) 8.0.3 中文汉化破解版
  2. TestNG官方文档中文版(1)-介绍
  3. javascript对行单击事件处理(委托事件)
  4. Extra Credits: Project Ten Dollar 10
  5. 判断这5个数值是否连续相邻
  6. 敏捷思维-架构设计中的方法学(11)精化和合并
  7. 笔记二:云上传与调用获取openid
  8. flex 实现图片播放 方案二 把临时3张图片预加载放入内存
  9. 数据时代,嵌入式工程师必须知道的八大加密算法
  10. [HAOI 2012]音量调节