数据库事务知识点总结

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 满足ACID(原子性,一致性,隔离性,持久性)。

原子性:要么全部执行,要么全部不执行。原子性消除了操作子集的可能性,如购物系统中只执行了付款生成订单的操作,却没有执行库存-1的操作。

一致性:事务在完成时,必须使所有的数据都保持一致状态。举例,在两个账户中总共存有5000块钱,无论两个账户之间怎么相互转账,两个账户总额一定还是5000

隔离性:由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。如T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。这称为隔离性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。

持久性:事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。

单个controller中有两个service,第一个service成功执行,第二个service失败则不会让第一个回滚

解决方案:将两个service服务放入一个service当中。使用相同的DAO。

单个controller中,在一个service中执行数据库操作并调用另一个service执行数据库操作,抛出异常过后发现事务一起回滚了(事务的传播属性行为PROPAGATION_REQUIRED ,service2在执行的时候发现service1已经开启了事务,这时候便直接加入第一个事务,如果第一个没有开启一个事务,则service2自己开启一个事务,这样不管是在第一个还是第二个service中出现异常都能回滚)。

事务的传播属性参考:https://blog.csdn.net/mawenshu316143866/article/details/81281443

    1.PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(也是spring默认的)。

 2.PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

 3.PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。

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() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)

 5.PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

事务2在 NOT_SUPPORTED 行为下,会挂起当前事务。与 REQUIRES_NEW 行为不同的是,它在挂起之后不会在尝试开启新的事务。也就是这一点的区别决定了 NOT_SUPPORTED 行为与 REQUIRES_NEW 行为。比如用于事务操作之间的日志记录等

 6.PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。

MANDATORY 行为下当前连接具备事务,会抛出异常,这种行为一般很少使用。

 7.PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,


事务的隔离级别:

脏读:一个事务读取到了另外一个事务没有提交的数据。读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被除撤消,而此时T1把已修改过的数据又恢复原值,T2读到的数据与数据库的数据不一致,则T2读到的数据就为“脏”数据,即不正确的数据。例如:一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。

脏读的解决方案:解决脏读问题:修改时加排他锁,直到事务提交后才释放,读取时加共享锁,读取完释放事务1读取数据时加上共享锁后(这样在事务1读取数据的过程中,其他事务就不会修改该数据),不允许任何事务操作该数据,只能读取,之后1如果有更新操作,那么会转换为排他锁,其他事务更无权参与进来读写,这样就防止了脏读问题。但是当事务1读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时事务1修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。

幻读:幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。按一定条件从数据库中读取了某些记录后,T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录消失T1按一定条件从数据库中删除某些数据记录后,T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

不可重复读指事务T1读取数据后,事务T2执行更新操作,使T1无法读取前一次结果。不可重复读包括三种情况:事务T1读取某一数据后,T2对其做了修改,当T1再次读该数据后,得到与前一不同的值。

更新丢失当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。如上例。再例如,两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。

共享锁:(读锁)共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

排他锁:(写锁)排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

隔离级别:

     ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

  ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。(mysql默认级别)

  ③ Read committed (读已提交):可避免脏读的发生。

  ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

推荐阅读更多精彩内容