啥是数据库事务?
数据库事务有严格的定义,必须同时满足4个特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称ACID。
- 原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交,事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
- 一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。
- 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。
- 持久性:一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
数据“一致性”是最终目标,其他的特性都是为达到这个目标的措施、要求或手段。
数据库管理系统一般采用重执行日志保证原子性、一致性和持久性,重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退出,数据库即可以根据重执行日志撤销已经执行的操作。对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作。
数据库锁机制
数据库通过锁的机制解决并发访问的问题。
按锁定的对象的不同,一般可以分为表锁定和行锁定,前者对整个表锁定,后者对表中特定行进行锁定。
从并发事务锁定的关系上看,可以分为共享锁定和独占锁定。共享锁定会防止独占锁定,但允许其他的共享锁定。而独占锁定既防止其他的独占锁定,也防止其他的共享锁定。
事务隔离级别
ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别。不同的事务隔离级别能够解决的数据并发问题的能力是不同的。
- READ UNCOMMITTED(未提交读):事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read),使用这个级别的数据库拥有最高的并发性和吞吐量。在实际应用中一般很少使用。
- READ COMMITTED(提交读):大多数数据库系统的默认隔离级别都是这个(但MySQL不是)。这个级别满足隔离性的定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能得到不一样的结果。
- REPEATABLE READ(可重复读):该级别解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但还是没解决幻读(Phantom Read)的问题。所谓幻读:指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。不过MySQL的InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。该级别是MySQL的默认事务隔离级别。
- SERIALIZABLE(可串行化):该级别是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。该级别会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
spring事务
spring事务使用
spring事务注解使用:@Transactional(isolation= Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
spring的事务抽象包括三个主要接口,即PlatformTransactionManager,TransactionDefinition以及TransactionStatus。
1.3.1.2.1. TransactionDefinition
1.3.1.2.1.1. TransactionDefinition简介
org.springframework.transaction.TransactionDefinition主要定义了有哪些事务属性可以指定,这包括:
事务的隔离级别(Isolation)
事务的传播行为(Propagation Behavisor)
事务的超时时间(Timeout)
是否为只读事务(ReadOnly)
TransactionDefinition内定义了五个常量用于标志可供选择的隔离级别:
ISOLATION_DEFAULT. 如果指定隔离级别为ISOLATION_DEFAULT,则表示使用数据库默认的隔离级别,通常情况下是“read committed”;
ISOLATION_READ_UNCOMMITTED. 对应“Read Uncommitted”隔离级别,无法避免脏读,不可重复读和幻读;
ISOLATION_READ_COMMITTED. 对应“Read Committed”隔离级别,可以避免脏读,但无法避免不可重复读和幻读;
ISOLATION_REPEATABLE_READ. 对应“Repeatable read”隔离级别,可以避免脏读和不可重复读,但不能避免幻读;
ISOLATION_SERIALIZABLE. 对应“Serializable”隔离级别,可以避免所有的脏读,不可重复读以及幻读,但并发性效率最低;
1.8. 业务方法的传播行为
TransactionDefinition针对事务的传播行为提供了以下几种选择,除了PROPAGATION_NESTED是spring特有的外,其他的传播行为的语义与CMT基本相同:
PROPAGATION_REQUIRED. 如果当前存在一个事务,则加入当前事务;如果不存在任何事务,则创建一个新的事务。总之,要至少保证在一个事务中运行。PROPAGATION_REQUIRED通常作为默认的事务传播行为。
PROPAGATION_SUPPORTS. 如果当前存在一个事务,则加入当前事务;如果当前不存在事务,则直接执行。 对于一些查询方法来说,PROPAGATION_SUPPORTS通常是比较合适的传播行为选择。 如果当前方法直接执行,那么不需要事务的支持;如果当前方法被其他方法调用,而其他方法启动了一个事务的时候,使用PROPAGATION_SUPPORTS可以保证当前方法能够加入当前事务并洞察当前事务对数据资源所做的更新。 比如说,A.service()会首先更新数据库,然后调用B.service()进行查询,那么,B.service()如果是PROPAGATION_SUPPORTS的传播行为, 就可以读取A.service()之前所做的最新更新结果,而如果使用稍后所提到的PROPAGATION_NOT_SUPPORTED,则B.service()将无法读取最新的更新结果,因为A.service()的事务在这个时候还没有提交(除非隔离级别是read uncommitted):
Figure 1.9. PROPAGATION_SUPPORTS可能场景
PROPAGATION_MANDATORY. PROPAGATION_MANDATORY强制要求当前存在一个事务,如果不存在,则抛出异常。 如果某个方法需要事务支持,但自身又不管理事务提交或者回滚的时候,比较适合使用PROPAGATION_MANDATORY。 你可以参照《JAVA TRANSACTION DESIGN STRATEGIES》一书中对REQUIRED和MANDATORY两种传播行为的比较来更深入的了解PROPAGATION_MANDATORY的可能应用场景。
PROPAGATION_REQUIRES_NEW. 不管当前是否存在事务,都会创建新的事务。如果当前存在事务的话,会将当前的事务挂起(suspend)。 如果某个业务对象所做的事情不想影响到外层事务的话,PROPAGATION_REQUIRES_NEW应该是合适的选择,比如,假设当前的业务方法需要向数据库中更新某些日志信息, 但即使这些日志信息更新失败,我们也不想因为该业务方法的事务回滚而影响到外层事务的成功提交,因为这种情况下,当前业务方法的事务成功与否对外层事务来说是无关紧要的。
PROPAGATION_NOT_SUPPORTED. 不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务的话,当前事务原则上将被挂起(suspend),但要依赖于对应的PlatformTransactionManager实现类是否支持事务的挂起(suspend),更多情况请参照TransactionDefinition的javadoc文档。 PROPAGATION_NOT_SUPPORTED与PROPAGATION_SUPPORTS之间的区别,可以参照PROPAGATION_SUPPORTS部分的实例内容。
PROPAGATION_NEVER. 永远不需要当前存在事务,如果存在当前事务,则抛出异常。
PROPAGATION_NESTED. 如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与PROPAGATION_REQUIRED的行为类似,即创建新的事务,在新创建的事务中执行。 PROPAGATION_NESTED粗看起来好像与PROPAGATION_REQUIRES_NEW的行为类似,实际上二者是有差别的。 PROPAGATION_REQUIRES_NEW创建的新事务与外层事务属于同一个“档次”,即二者的地位是相同的,当新创建的事务运行的时候,外层事务将被暂时挂起(suspend); 而PROPAGATION_NESTED创建的嵌套事务则不然,它是寄生于当前外层事务的,它的地位比当前外层事务的地位要小一号,当内部嵌套事务运行的时候,外层事务也是出于active状态:
Figure 1.10. PROPAGATION_REQUIRES_NEW与PROPAGATION_NESTED创建的事务的区别
PROPAGATION_NESTED可能的应用场景在于,你可以将一个大的事务划分为多个小的事务来处理,并且外层事务可以根据各个内部嵌套事务的执行结果来选择不同的执行流程。 比如,某个业务对象的业务方法A.service()可能调用其他业务方法B.service()向数据库中插入一批业务数据,但当插入数据的业务方法出现错误的时候(比如主键冲突),我们可以在当前事务中捕捉前一个方法抛出的异常,然后选择另一个更新数据的业务方法C.service()来执行, 这个时候,我们就可以把B.service()和C.serivce()方法的传播行为指定为PROPAGATION_NESTED[9],如果用伪代码演示的话,看起来如下:
/** * PROPAGATION_REQUIRED */ A.service() { try { // PROPAGATION_NESTED B.service(); } catch(Exception e) { // PROPAGATION_NESTED C.service(); } }
不过,并非所有的PlatformTransactionManager实现都支持PROPAGATION_NESTED类型的传播行为,现在只有org.springframework.jdbc.datasource.DataSourceTransactionManager在使用JDBC3.0数据库驱动的情况下才支持(当然,数据库和相应的驱动程序也需要提供支持),另外, 某些JtaTransactionManager也可能提供支持,但JTA规范并没有要求提供对嵌套事务的支持。
TransactionDefinition提供了TIMEOUT_DEFAULT常量定义,用来指定事务的超时时间,TIMEOUT_DEFAULT默认值为-1,这会采用当前事务系统默认的超时时间,你将可以通过TransactionDefinition的具体实现类提供自定义的事务超时时间。
TransactionDefinition提供的最后一个重要信息就是将要创建的是否是一个只读(ReadOnly)的事务,如果你需要创建一个只读的事务的话,可以通过TransactionDefinition的相关实现类进行设置。 只读的事务仅仅是给相应的ResourceManager提供一种优化的提示,但最终是否提供优化,则由最终的ResourceManager决定。对于一些查询来说,我们通常会希望它们采用只读事务。
1.3.1.2.1.2. TransactionDefinition相关实现
TransactionDefinition仅仅是一个接口定义,要为PlatformTransactionManager创建事务提供信息,需要有相应的实现类提供支持。 TransactionDefinition的相关实现类虽然不多,但为了便于理解,我们依然将他们划分为“两派”:
org.springframework.transaction.support.DefaultTransactionDefinition是TransactionDefinition接口的默认实现类, 他提供了各事务属性的默认值,并且通过它的setter方法,你可以更改这些默认设置。这些默认值包括:
propagationBehavior = PROPAGATION_REQUIRED
isolationLevel = ISOLATION_DEFAULT
timeout = TIMEOUT_DEFAULT
readOnly = false
org.springframework.transaction.interceptor.TransactionAttribute是继承自TransactionDefinition的接口定义, 主要面向使用Spring AOP进行声明式事务管理的场合,它在TransactionDefinition定义的基础上添加了一个rollbackOn方法:
boolean rollbackOn(Throwable ex);这样,我们可以通过声明的方式指定业务方法在抛出哪些的异常的情况下可以回滚(rollback)事务。
TransactionAttribute的默认实现类是DefaultTransactionAttribute,他同时继承了DefaultTransactionDefinition, 在DefaultTransactionDefinition的基础上追加了rollbackOn的实现,DefaultTransactionAttribute的实现指定当异常类型为“unchecked exception”的情况下将回滚(rollback)事务。
DefaultTransactionAttribute之下有两个实现类,即RuleBasedTransactionAttribute以及DelegatingTransactionAttribute。 RuleBasedTransactionAttribute允许我们同时指定多个回滚规则,这些规则以包含org.springframework.transaction.interceptor.RollbackRuleAttribute或者org.springframework.transaction.interceptor.NoRollbackRuleAttribute的List形式提供, RuleBasedTransactionAttribute的rollbackOn将使用传入的异常类型与这些回滚规则进行匹配,然后再决定是否要回滚事务。
DelegatingTransactionAttribute是抽象类,它存在的目的就是被子类化,DelegatingTransactionAttribute会将所有方法调用委派给另一个具体的TransactionAttribute实现类, 比如DefaultTransactionAttribute或者RuleBasedTransactionAttribute,不过,除非不是简单的直接委派(什么附加逻辑都不添加),否则,实现一个DelegatingTransactionAttribute是没有任何意义的。
1.3.1.2.2. TransactionStatus
org.springframework.transaction.TransactionStatus接口定义表示整个事务处理过程中的事务状态, 我们将更多时候在编程式事务中使用该接口。
在事务处理过程中,我们可以使用TransactionStatus进行如下工作:
使用TransactionStatus提供的相应方法查询事务状态;
通过setRollbackOnly()方法标记当前事务以回滚(rollback);
如果相应的PlatformTransactionManager支持Savepoint,可以通过TransactionStatus在当前事务中创建内部嵌套事务;
TransactionStatus的实现层次比较简单,见下图:
Figure 1.12. TransactionStatus继承层次
org.springframework.transaction.SavepointManager是在JDBC3.0的基础上对Savepoint的支持提供的抽象, 通过继承SavepointManager,TransactionStatus获得可以管理Savepoint的能力,从而支持创建内部嵌套事务。
org.springframework.transaction.support.AbstractTransactionStatus为TransactionStatus的抽象类实现, 主要为其他实现子类提供一些“公共设施”,它下面主要有两个子类, DefaultTransactionStatus和SimpleTransactionStatus, 其中,DefaultTransactionStatus是spring事务框架内部使用的主要TransactionStatus实现类,spring事务框架内的各个TransactionManager的实现大都借助于DefaultTransactionStatus来记载事务状态信息。 SimpleTransactionStatus在spring框架内部的实现中没有使用到,目前来看,主要用于测试目的。
1.3.1.2.3. PlatformTransactionManager
PlatformTransactionManager是spring事务抽象框架的核心组件,关于它的定义以及作用我们之前已经提过了,所以,这部分我们不妨更多的关注一下PlatformTransactionManager整个的层次体系以及针对不同数据访问技术的实现类。
PlatformTransactionManager整个的抽象体系基于Strategy模式,由PlatformTransactionManager对事务界定进行统一抽象,而具体的界定策略的实现则交由具体的实现类。 下面我们先来看一下有哪些实现类可供我们使用...
1.3.1.2.3.1. PlatformTransactionManager实现类概览
PlatformTransactionManager的实现类可以划分到面向局部事务和面向全局事务两个分支:
面向局部事务的PlatformTransactionManager实现类. spring为各种数据访问技术提供了现成的PlatformTransactionManager实现支持,以下列表给出了各种数据访问技术与它们对应的实现类的关系:
Table 1.1. 数据访问技术与PlatformTransactionManager实现类对应关系
数据访问技术 PlatformTransactionManager实现类 JDBC/iBatis DataSourceTransactionManager Hibernate HibernateTransactionManager JDO JdoTransactionManager JPA(Java Persistence API) JpaTransactionManager TopLink TopLinkTransactionManager JMS JmsTransactionManager JCA Local Transaction CciLocalTransactionManager
在这些实现类当中,CciLocalTransactionManager可能是比较少见的实现,CCI的意思是Common Client Interface, CciLocalTransactionManager主要是面向JCA的局部事务(Local Transaction),本书不打算对JCA的集成做过多的阐述,读者如果在实际项目中需要使用到JCA进行EIS(Enterprise Information System)系统集成,你可以从spring的参考文档获得使用spring提供的JCA集成支持的足够信息。有了这些实现类,我们在使用spring的事务抽象框架进行事务管理的时候,只需要根据当前使用的数据访问技术选择对应的PlatformTransactionManager实现类即可。