什么是事务:
数据库的事务是指一组sql语句组成的数据库逻辑处理单元,在这组的sql操作中,要么全部执行成功,要么全部执行失败。
就假如小明给小红存钱,逻辑上一定是小明扣钱与小红加钱是一起成功或失败的。(不可以出现只有一方成功的情况)
事务的四大特征:
事务包含原子性(Atomicity)、一致性(Consistent)、隔离性(Isalotion)、持久性(Durable),简称为ACID。
原子性:是指对数据的修改要么全部执行成功,要么全部失败,实现事务的原子性,是基于日志的Redo/Undo机制。
日志的Redo/Undo机制 是指它们将所有对数据的更新操作都写到日志中,Redo log用来记录某数据块被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据;Undo log是用来记录数据更新前的值,保证数据更新失败能够回滚。
一致性:是指执行事务前后的状态要一致,可以理解为数据一致性。
隔离性:指事务之间相互隔离,不受影响,这个与事务设置的隔离级别有密切的关系。
持久性:持久性则是指在一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、更新将会持久化到数据库中。
事务的隔离级别:
- Read uncommitted (读未提交):最低级别,以上问题均无法解决。
- Read committed (读已提交):读已提交,可避免脏读情况发生。
- Repeatable Read (可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
- Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。
以上的隔离级别是从低到高的,越高的隔离级别性能就越差。Mysql默认的隔离级别为第三级Repeatable Read。
问题 | 含义 |
---|---|
脏读 | A事务读取B事务尚未提交的更改数据 |
不可重复读 | 不可重复读是指A事务读取了B事务已经提交的更改数据。假如A在取款事务的过程中,B往该账户转账100,A两次读取的余额发生不一致。 |
幻读 | A事务读取B事务提交的新增数据,会引发幻读问题 |
不可重复读和幻读的区别是:不可重复读是指读到了已经提交的事务的更改数据(修改或删除),幻读是指读到了其他已经提交事务的新增数据。
对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据。
查看Mysql数据库当前的隔离级别
select @@tx_isolation;
设置Mysql数据库的隔离级别
set tx_isolation = '隔离级别名称'
在工作中,一般项目会使用(Read Commited)读已提交作为隔离级别。因为RC比RR在锁方面,由于RC不使用间隙锁解决幻读问题,所以并发要好于RR,并且 不容易出现死锁 。
我们了解完事务的隔离后再来了解一下我们如何在工作中使用事务吧。
在SpringBoot中有这么一个注解可以进行事务管理:
@Transactional(rollbackFor = Exception.class)
当我们执行某个方法需要进行事务管理时,我们就需要在类上添加此注解。
rollbackFor是指发生了什么异常的时候会自动回滚。
当我们需要手动回滚的时候,我们可以在方法中调用下面的方法进行回滚。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
SpringBoot中的事务管理需要注意如下问题:
1.方法的修饰符必须为public,可以使用final,不可以使用static等其他修饰符。
2.会默认对RuntimeException及其子类进行回滚,遇到Exception及其子类则不会默认进行回滚,需要使用rollbackFor属性变为Exception。
3.如果 同一个类的普通方法A调用事务方法B则方法B的事务不会生效 ,因为 同类方法调用不走代理,无法增强(重要)
4.在多线程的情况下, 事务是不会传递到子线程中 的,需要在子方法中单独添加事务注解。
5.如果方法中有try…catch…处理,try中的代码则脱离了事务的管理,若需要事务生效,需要手动在catch中throw new RuntimeException(“XXXXX”)。
如何处理同一个类的事务方法互相调用时事务失效问题?
首先我们要在启动类上添加注解,启用exposeProxy。
@EnableAspectJAutoProxy(exposeProxy = true)
当我们在方法A中调用事务方法B的时候,创建代理对象,调用对象的事务方法。
//获得代理对象
B b = (B) AopContext.currentProxy();
//调用对象b的事务方法method
b.method();
在事务的传播过程中,A方法调用B方法,需要B方法单独开一个事务的时候,我们要在事务注解中添加此属性。
@Transactional(propagation = Propagation.REQUIRES_NEW)
表示会单独新开一个事务。