博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】Spring MySQL 事务隔离级别,传播机制,savepoint
阅读量:4979 次
发布时间:2019-06-12

本文共 6179 字,大约阅读时间需要 20 分钟。

 MySQL的四种事务隔离级别

spring事物的七种事物传播属性行为及五种隔离级别

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED区别

savepoint 

 mysql 可重复读 通过加锁解决幻读问题

手动回滚事务

 


 

一、事务的基本要素(ACID)

  1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

   2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二、事务的并发问题

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

三、MySQL事务隔离级别

   mysql默认的事务隔离级别为repeatable-read

事务隔离级别 脏读 不可重复读 幻读
读未提交(read uncommitted)
读已提交(read committed)
可重复读(repeatable read)
串行化(serializable)

补充:

  1、事务隔离级别为读已提交时,写数据只会锁住相应的行

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

  MYSQL可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)

  如果select 操作加锁(LOCK IN SHARE MODE, for update),则不使用快照,使用最新当前版本数据,

      如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。

      本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

      可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。

If you want to see the “freshest” state of the database, you should use either the READ COMMITTED isolation level or a locking read:

SELECT * FROM t_bitfly LOCK IN SHARE MODE;

      结论:MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

  3、事务隔离级别为串行化时,读写数据都会锁住整张表

   4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

   5、MYSQL MVCC实现机制参考链接:

   6、关于next-key 锁可以参考链接:

 

Mysql操作:

select @@tx_isolation; //查询隔离级别

set session transaction isolation level read uncommitted;//设置隔离级别

start transaction;//开启事务

rollback;//回滚事务

commit;//提交事务

 

 

 


 

 

首先,说说什么事务(Transaction)。
事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。

其中spring七个事物传播属性:

 PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
 PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
 PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
 PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
 PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
 PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
 PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
则进行与PROPAGATION_REQUIRED类似的操作。
五个隔离级别:
ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应;
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。

ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取

该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证

一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,

不可重复读外,还避免了幻像读。
关键词:
1)幻读:事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;
2)不可重复读取:事务1读取记录时,事务2更新了记录并提交,事务1再次读取时可以看到事务2修改后的记录;
3)脏读:事务1更新了记录,但没有提交,事务2读取了更新后的行,然后事务T1回滚,现在T2读取无效。


 savepoint 

NESTED 嵌套事务里用的,spring 动态拦截器 进入被NESTED事务标记的方法之前,会设置保存点 SAVEPOINT ·savepoint_1`;

如果方法出现异常执行回滚到savepoint,ROLLBACK TO SAVEPOINT;

如果没有异常,则方法执行完后,释放保存点,RELEASE SAVEPOINT;

嵌套的外层事务commit时,一起提交到数据库;

NESTED 嵌套事务,使用存在的事务,并且使用存在的数据库连接,conn,sqlsession

 


 事务套事务Bug记录分析:  

package com.jd.project.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;@Servicepublic class ServiceA {        @Autowired    private ServiceB serviceB;        @Transactional(propagation = Propagation.REQUIRED)    public void doSometing() {                saveToDb1();                try {            serviceB.doSometing();        } catch (Exception e) {            e.printStackTrace();        }                saveToDb2();    }    private void saveToDb1() {        System.out.println("saveToDb1");    }    private void saveToDb2() {        System.out.println("saveToDb2");    }}
package com.jd.project.service;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;@Servicepublic class ServiceB {        @Transactional(propagation = Propagation.REQUIRED)    public void doSometing() {        //插入数据到数据库        insertData();                throw new RuntimeException("抛出异常,触发事务回滚");    }    private void insertData() {        System.out.println("insertData");    }}

  现象描述: 调用类调用ServiceA的doSomething()方法, saveToDb1()、saveToDb2()没有发生异常,serviceB.doSometing() 异常也被捕获,却发生了回滚,saveToDb1(), saveToDb2()没有生效。

  原因分析:serviceB.doSometing()的事务隔离级别为Propagation.REQUIRED,使用了外层已经的存在的事务,serviceB.doSometing()发生异常,事务被标记为rollback-only,等saveToDb2()执行完,事务触发了回滚,导致saveToDb1(), saveToDb2(),没有生效。

  修复方法:serviceB.doSometing()的事务隔离级别为Propagation.REQUIRED_NEW,或者Propagation.PROPAGATION_NESTED ;

Propagation.REQUIRED_NEW为新建事务,新建会话sqlsession,

Propagation.PROPAGATION_NESTED,使用当前事务,当前会话sqlsession, 但是提供savepoint,发生异常时,回滚到savepoint, serviceA的后面的saveToDb2()方法会继续执行。

 


 

手动回滚事务

手动回滚:

方法1:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)

 方法2:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理

@Transactional      public String commonMoney(Receipt rpt,Moneyrecord mors){          rpt.setState(1);          int a=dao.insert(rpt);          if(a<=0) return"缴费失败";          mors.setPric(rpt.getPic());          mors.setExid(rpt.getPid());          mors.setState(1);          boolean tf=mrs.custom(mors);          if(!tf){              TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();              return"余额不足";          }          return "OK";      }

 

转载于:https://www.cnblogs.com/ccgblog/p/9554219.html

你可能感兴趣的文章
生活中的物理学(电学)
查看>>
中医文化 —— 穴位
查看>>
从二叉搜索树到平衡二叉搜索树
查看>>
推理集 —— 心理
查看>>
队列&栈的研究
查看>>
axis2 实例学习
查看>>
开发进度02
查看>>
构建自己的embedded linux系统
查看>>
【WCF系列一】WCF入门教程(图文) VS2012
查看>>
mysql 匹配 findinset
查看>>
[python]做一个简单爬虫
查看>>
最长递增子序列
查看>>
Eclipse快捷键
查看>>
常用标签与表格
查看>>
SQL Server2008 学习笔记(三) 数据库管理
查看>>
ANDROID笔记:Button的简单使用
查看>>
如何为你的美术妹子做Unity的小工具(一)
查看>>
read()、readline()、readlines()区别
查看>>
PAT:1028. List Sorting (25) AC
查看>>
Runtime理解
查看>>