数据库事务
数据库事务的概念以及原理
认识事务
事务的概念:数据库事务是访问并可能更新数据库中各种数据项的一个程序执行单元。
事务的组成:一个数据库事务通常包含对数据库进行读或写的一个操作序列。
一个典型的数据库事务:
BEGIN TRANSACTION//事务开始
SQL1
SQL2
COMMIT/ROLLBACK//事务提交或回滚
事务的相关特性
- 数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。
- 构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部执行不成功。
- 构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
- 以上即使在数据库出现故障以及并发事务存在的情况下依然成立。具备隔离性。
事务使系统能够更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。
总结:
- 原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
- 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。
- 隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。
- 持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
事务并发异常
- 丢失更新(Lost Update):丢失更新是指事务覆盖了其他事务对数据的已提交修改,导致其他事务的修改好像丢失了一样。
- 脏读(Dirty Read):脏读是指一个事务读取了另一个事务未提交的数据。
- 幻读(Phantom Read):指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致。
- 不可重复读(Unrepeatable Read):指一个事物对同一数据的读取结果前后不一致。
事务的隔离级别
事务并发操作同一批数据的时候所导致的问题可以通过设置隔离级别来解决。
隔离级别的分类(从低到高)
- 读未提交(READ UNCOMMITTED)
- 读已提交(READ COMMITTED)–Oracle默认级别
- 可重复读(REPEATABLE READ)–MySQL默认级别
- 串行化(SERIALIZABLE)
隔离级别从小到大安全性越来越高,但是效率越来越低
事务并发异常与隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读提交 | × | √ | √ |
可重复读 | × | × | √ |
可串行化 | × | × | × |
在使用,可重复读时,MySQL等成熟的数据库已经通过锁机制在默认隔离级别的时候,就避免了幻读的出现。
丢失更新
如果多个线程操作,基于同一个查询结构对表中的记录进行修改,那么后修改的记录将会覆盖前面修改的记录,前面的修改就丢失掉了,这就是丢失更新。分为两类:回滚丢失和覆盖丢失。
解决办法
-
悲观锁机制:假定这样的问题是高概率的,最好一开始就锁住,免得更新老是出错。
- 添加共享锁方式:
select*from account lock in share mode;
当A事务把某一个数据用共享锁锁住之后,B事务就只能对该数据进行查看,不能进行任何修改操作。 - 添加排他锁方式:
select*from account for update;
当A事务把某一个数据用排他锁锁住之后,B事务不能对该数据进行任何操作。
- 添加共享锁方式:
-
乐观锁机制:假定这样的问题是小概率的,最后一步做更新的时候再锁住,免得锁住时间太长影响其他人做有关操作。
- 在表中增加一个类型是timestamp字段,并将其设置只要该表进行插入或修改操作时都会更新该字段为最新时间。
- 在修改数据时通过检查timestamp是否改变判断出当前更新基于的查询是否已经是过时的版本。
在用户并发数比较少且冲突比较严重的应用系统中选择悲观锁-排他锁方法,其他情况首先考虑乐观锁版本列更新的方法
数据库命令:
- 查看数据库版本:
select version()
; - 查看数据库现在的隔离级别:
select @@session.tx_isolation
; - 修改隔离级别:
set @@session.tx_isolation=级别参数
; - 级别参数:
READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ,SERIALIZABLE
; - 开启事务:
start transaction
; - 提交/回滚:
commit/rollback
Spring-boot中的事务处理
@Transactional中的属性讲解
参数 | 功能 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transaction(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transaction(rollbackFor=RuntimeException.class) ,指定多个异常类:@Transaction({rollbackFor=RuntimeException.class,Exception.class}) 。默认异常指的是运行时异常或者error错误 |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称:@Transaction(rollbackForClassName="RuntimeException") ,指定多个异常类名称:@Transaction(rollbackForClassName="RuntimeException","Exception") |
noRollbackFor | 该属性用于设置不需要进行回滚的异常数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transaction(noRollbackFor=RuntimeException.class) ,指定多个异常类:@Transaction({noRollbackFor=RuntimeException.class,Exception.class}) 。 |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transaction(noRollbackForClassName="RuntimeException") ,指定多个异常类名称:@Transaction(noRrollbackForClassName="RuntimeException","Exception") |
propagation | 该属性用于设置事务的传播行为。例如:@Transaction(propagation=Propagation.NOT_SUPPORTED) |
timeout | 该属性用于设置事务的超时秒数,默认值为**-1**表示永不超时 |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。@Transactional(isolation=Isolation.READ_UNCOMMITTED) |
Transactional中的属性讲解:propagation
Transactional(propagation=Propagation.REQUIRED)
:如果有事务,那么加入事务,没有的话新建一个(默认情况下)Transactional(propagation=Propagation.NOT_SUPPORTED)
:容器不为这个方法开启事务;Transactional(propagation=Propagation.REQUIRES_NEW)
:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务;Transactional(propagation=Propagation.MANDATORY)
:必须在一个已有的事务中执行,否则抛出异常;Transactional(propagation=Propagation.NEVER)
:必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)Transactional(propagation=Propagation.SUPPORTS)
:如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务。
Transactional中的属性讲解:timeout
- 设置事务的超时时间,单位:秒;
- 属性解释:当某个业务运行的时间超过你的预期时,可以使用该属性来让该业务抛出异常并且强制回滚。