数据库事务4种隔离级别和7种传播行为
一、数据库隔离级别:是在在数据库操作中,为了有效保证并发读取数据的正确性提出的。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
数据库的几种隔离级别:
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
- READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
- REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
- SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。
Oracle支持两种事务隔离级别:
READ COMMITTED(默认事务隔离级别),SERIALIZABLE
MySQL支持四种事务隔离级别,其中REPEATABLE READ为默认事务隔离级别。
通过上面可以知道多事务同时运行,如果不采用以上四种隔离机制,可能会产生多个并发问题,其中包括脏读、不可重复读和幻读,下面就解释下这几种并发问题:
存在两个事物(T1,T2)同时运行
- 脏读:T1读取了已经被T2修改但还未提交的字段,由于某种原因,T2事物回滚,则T1读取的内容是临时且无效的。
- 不可重复读:T1读取一个字段,之后T2更新了该字段,T1在此读取该字段值发生了变化。
- 幻读:T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后T1在此读取该表会多出几行。
二、数据库事务的特性:原子性、一致性、隔离性、持久性
- 原子性:事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行,这种特性称为原子性。(简单地说就是,几个对于数据库的操作要么全执行,要么全不执行,即同时成功起作用或同时失败没影响)
- 一致性:事务一致性值得是在一个事务执行之前和执行之后数据库都必须处于一致性状态(中途是否一致不用管),这种特性称为一致性。(如果数据库的状态满足所有的完整性约束,就说该数据库是一致的。一致性处理数据库中对所有语义的保护。如:客户K1要向客户K2转账,K1账户减少的金额就是K2账户增加的金额,在转账之前K1和K2账户的金额之和与转账之后K1和K2账户的金额之和是一样的,在转账期间可能不满足这种一致性,但事务前后是数据库数据是一致的)
- 隔离性:隔离性指的是并发的事务是相互隔离的。(一个事务内部的操作及正在操作的数据必须封装起来,不被其它企图进行修改的事务看到)
- 持久性:持久性指当系统或介质发生故障时,确保已提交的更新不能丢失。(一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,可以经受任何系统故障,持久性通过数据库备份和恢复来保证)
三、传播行为
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
- 1: PROPAGATION_REQUIRED
- 加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
- 比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
- ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
- 的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
- 这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
- 提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
- 2: PROPAGATION_SUPPORTS
- 如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
- 3: PROPAGATION_MANDATORY
- 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
- 4: PROPAGATION_REQUIRES_NEW
- 这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
- 那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
- 他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
- 两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
- 如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
- 5: PROPAGATION_NOT_SUPPORTED
- 当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
- 那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
- 6: PROPAGATION_NEVER
- 不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
- 那么ServiceB.methodB就要抛出异常了。
- 7: PROPAGATION_NESTED
- 理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
- 而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
- 而Nested事务的好处是他有一个savepoint。