目录
一、数据库事务
1、什么是数据库事务
事务的概念:数据库事务是访问并可能更新数据库中各种数据项的一个程序执行单元
事务的组成:一个数据库事务通常包含对数据库进行读或写的一个操作序列(N多个读、或N多个写,N可为0,均为0时需标明这一个过程是一个事务)
一个典型的数据库事务:
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
事务的相关特性:
1.数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体
2.构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部执行不成功
3.构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态
4.以上即使在数据库出现故障以及并发事务存在的情况下依然成立
事务使系统能够更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。
2、事务的特性:ACID
1.原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败
2.一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态
3.隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样
4.持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失
3、事务的并发异常分析
1.丢失更新(Lost Update):事务覆盖了其他事务对数据已提交的修改,导致其他事务的修改好像丢失了一样
2.脏读(Dirty Read):一个事务读取了另一个事务未提交的数据
3.不可重复读(Unrepeatable Read):一个事务对同一数据的读取前后不一致(第二次读取了其他事务已经提交的数据)
4.幻读(Phantom Read)(幻象读):指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致(读取了其他事务提交的新增数据,幻读一般发生在数据统计事务中)
幻读与不可重复读区别:幻读一般针对统计性的工作,而不可重复读针对一条数据两次查询结果不一致
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
二、数据库事务隔离级别
4、事务的隔离级别
事务并发操作同一批数据的时候所导致的问题可以通过设置隔离级别来解决。(解决事务的并发异常)
隔离级别的分类(隔离程度从低到高,安全性越来越高,效率越来越低):
1.读未提交(READ UNCOMMITTED)
2.读已提交(READ COMMITTED)--SQLserver、Oracle默认级别
3.可重复读(REPEATABLE READ)--MySQL、innodb默认级别
4.串行化(SERIALIZABLE)
MySQL具有以上四种隔离级别,Oracle只有两种隔离级别:读已提交、串行化
事务并发异常与隔离级别(不同隔离级别可能会发生的并发异常):
读未提交:脏读、不可重复读、幻读
读已提交:不可重复读、幻读
可重复读:幻读(理论上会出现幻读,但MySQL、Oracle等成熟数据库已经解决了这个问题,实际不会出现)MySQL已经通过锁机制在可重复读隔离级别的时候,就避免了幻读的出现。
串行化:(串行化存在,无并发异常)
事务并发异常--丢失更新
第一类丢失更新--回滚丢失:A事务读取,B事务读取后提交更新,A事务更新后回滚,导致B事务的更新因A事务的回滚而丢失
第二类丢失更新--覆盖丢失:A事务读取,B事务读取后提交更新,A事务提交更新,导致B事务提交的更新被A事务的更新覆盖
SQL192没有定义第一类丢失更新这种现象,标准定义的所有隔离级别都不允许第一类丢失更新发生(不会有回滚丢失更新这种情况发生)
事务并发异常--丢失更新的解决办法(第二类丢失更新)
事务中的锁机制:悲观锁、乐观锁
1、悲观锁:假定丢失更新这样的问题是高概率发生的,最好一开始就锁住,免得更新总是出错。两种添加锁的方式:
添加共享锁方式:select * from table lock in share mode;(一个事务对数据添加共享锁,其他事务只能查看,如需修改必须等待持有锁的事务释放锁)
添加排它锁方式:select * from table for update;(一个事务对数据添加排它锁,其他事务不能查看,也不能修改数据,必须等待锁释放)
2、乐观锁:假定丢失更新这样的问题是小概率发生的,最后一步做更新的时候再锁住,免得锁住时间太长影响其他人对该数据做有关操作。加锁方式-版本列法:
在表中增加一个类型为timestamp的字段,设置当插入或修改操作时都会更新该字段为最新时间
在修改数据时通过检查timestamp是否改变判断出当前更新基于的查询是否已经是过时的版本
在用户并发数较少且冲突比较严重的应用系统中选择悲观锁-排它锁的方法,其他情况推荐使用乐观锁-版本列法的方法
我理解的隔离级别:
1.读未提交:读取其他事务未提交的数据,所以会出现脏读。commit之前,数据已经提交到服务器了,只是写在日志里,没有正式提交。只有commit提交之后,才真正再从日志中更新到mysql表中。
2.读已提交:读取真实的数据,所以不会出现脏读
3.可重复读:针对行加锁,不能修改数据,但可新增一行数据,所以会出现幻读,但实际上不会出现,因为MySQL、Oracle的内部实现避免了这个问题
4.串行化:对整张表加锁,不会出现幻读,串行执行不会有并发问题,但效率极低,不建议使用
总结:
1、事务隔离级别为读提交时,写数据只会锁住相应的行
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。
4、事务隔离级别为串行化时,读写数据都会锁住整张表
5、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
5、数据库命令
查看数据库版本:select version();
查看数据库现在的隔离级别:select @@session.tx_isolation;
修改隔离级别:set @@session.tx_isolation='级别参数';
级别参数:READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ(MySQL、innodb的默认隔离级别),SERIALIZABLE;
开启事务:start transaction;
提交/回滚:commit/rollback;
三种查询和设置隔离级别的方式:
select @@tx_isolation
set tx_isolation = 'XXXXXX'
select @@session.tx_isolation
set @@session.tx_isolation = ‘xxx’ (如果设置了tx 则不需要设置session)
select @@global.tx_isolation
set @@global.tx_isolation = ‘xxx’
设置mysql的隔离级别:
set session transaction isolation level 隔离级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;
测试SQL:
start transaction;
select * from account where id=1;
update account set balance=balance-500 where id=1;
rollback;
update account set balance=balance+100 where id=1;
commit;
update account set balance=1000;
select @@tx_isolation;
set tx_isolation = 'READ-UNCOMMITTED';
set tx_isolation = 'REPEATABLE-READ';
三、springboot中的事务处理
6、springboot中事务的使用
select @@IDENTITY:查询最近一次ID自增值的值(select * from student where id = (select @@IDENTITY))
@MapperScan(""):启动类设置Mybatis自动扫包路径,用于扫描基于注解的SQL映射语句
@Transactional:开启事务注解
7、@Transactional中属性讲解
1、readOnly:该属性设置当前事务是否为只读事务,设置为true表示只读,false则表示可以读写,默认值为false。例如:@Transactional(readOnly=true),在确保只有查询的方法上使用readOnly=true后,spring会进行优化这个方法,代表是一个只读的connation数据库连接,效率会高很多,查询数据量大时使用可提高查询速度。
2、rollbackFor:该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class),指定多个异常类:@Transactional(rollbackFor={RuntimeException.class,Exception.class})。默认是RuntimeException和error,发生这两种异常时事务进行回滚,需要自定义异常时可使用rollbackFor/noRollbackFor属性定义。
3、rollbackForClassName:该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException"),指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
4、noRollbackFor:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class),指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class,Exception.class})
5、noRollbackForClassName:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException"),指定多个异常类名称:@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
6、propagation:该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED)
7、timeout:该属性用于设置事务的超时秒数,默认值为-1表示永不超时(事务对应的方法指定时间内没有运行结束,则强制结束)
8、isolation:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况。通常使用数据库的默认隔离级别即可,基本不需要进行设置。
@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
设置事务的超时时间,单位秒
当某个业务运行的时间超过你的预期时,可以使用该属性来让该业务抛出异常并且强制回滚。
应用场合:比如银行取款失败时,可使用该属性来强制结束业务,进行回滚,减少用户等待时间。
@Transactional中属性讲解:isolation
设置事务隔离级别
@Transactional(isolation=Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读、不可重复读、幻读)基本不使用
@Transactional(isolation=Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读、幻读)Oracle默认级别
@Transactional(isolation=Isolation.REPEATABLE_READ):可重复读(会出现幻读)MySQL默认级别
@Transactional(isolation=Isolation.SERIALIZABLE):串行化
8、事务在实战中的应用场景
实体类属性字段与数据库字段不对应时,通过@Results注解实现转换:
@Results({@Result(property="userId",column="user_id"),@Result(property="goodId",column="good_id")})