今天在整理资料的时候发现了之前学习事务的时候的一些学习笔记,顺便写篇blog记录一下备以后查验。
在我们日常系统开发当中,我们是不是不可避免的要对一些数据资源 进行访问,但是我们怎么来保证我们对数据资源的访问不会破坏数据资源的完整性呢?这个时候就需要事务了,正是引入了事务的概念,我们平时对数据资源进行操作的时候才不会破坏数据资源的完整性或者说是不变量约束 。
前面从为什么需要事务的角度,我们意识到了事务其实就是用来保护系 统处于一致性性状态的一种措施。也可以说是一系列原子操作 的集合。
前面从事务目的的角度说明了事务到底是什么,那么事务又是达到保证数据资源完整性的目的的呢?这需要我们来了解一下事务的特征,事务一共有四个特性 (ACID) ,分别是原子性( Atomicity), 一致性( Consistency), 隔离性( Isolation), 持久性( Durability).
何为原子性?之前说了事务需要保证数据资源的完整性,那么我们就需要控制对数据资源的访问,使得对数据资源进行访问的操作要么全部成功,否则只要一个操作失败,那么全部失败(虽然有点极端 ^_^ ,但是很必要),这样数据资源就不会因为一部分操作成功,但是一部分操作失败而引起系统不一致。
何为一致性呢?一致性就是说,当我们在对数据资源的一次访问过程中,在访问之前和访问之后,数据资源的状态都要处于一种完整的,满足不变量约束的状态。(比如银行转账,当从账户 A 转账到账户 B 后,账户 A 和账户B 的金额总和是不变的,这就是一种不变量约束)。
其实一致性也说出了事务的心声,这也正是事务的目的,但是要想实现此目的,事务必须满足其它的三条特征,原子性,隔离性,持久性是一致性的必要条件。
何为隔离性?隔离性主要是考虑到了系统的并发操作,但系统对数据资 源进行访问的时候,可能同时有好多个并发操作同时进行,这样为了保证系 统的一致性,事务必须具有一定的隔离性,使得并发执行的一些列操作互相 不要产生干扰。
总之,事务的隔离性,主要是为了保证在多线程环境下,数据资源的一 致性。
何为持久性?持久性就是指在事务结束的时候,事务的结果应该被持久 的保存下来,此特征也是为一致性来服务的,试想一下,假如事务成功了, 但是此时突然断电或者其它的故障,那么此时事务的结果还是应该要保存下 来,不然系统的不变量约束就遭到了破坏。
以上是事务的四特征,下面主要来说说事务的隔离性问题,对于事务的隔离性问题, ANSI 标准规定了四个隔离级别,如下 :
在说隔离级别以前,我们先来明确两个问题,读取事务和写事务,读取事务只是对数据库进行读操作,不会引起数据库状态变化,而写事务要对数据库进行更新。下面就以读事务和写事务来加以描述:
读取未提交隔离级别是最低的一种隔离级别,其中读事务不会阻塞任 何事务,写事务阻塞写事务,但是不阻塞读事务,正是因为写事务不阻塞读 事务,所以就造成读取事务读到的数据可能是脏的,这也就是著名的“脏读 问题”,同时因为读事务没有阻塞写事务,所以在读事务中的两次读取操作 可能读到不同的数据,这也就造成了著名的“不可重复读问题”,同理此种 情况下也存在“幻影读”问题。
读取已提交隔离级别中,读事务不会阻塞任何事务,但是写事务会阻塞 读事务和其它写事务,此时因为写事务会阻塞读事务,所以不会出现“脏读” 问题,但是因为此时读事务还是不阻塞写事务,所以“不可重复读”问题还 会出现,同时“幻影读”问题还是会出现。
可重复读隔离级别中,读事务会阻塞写事务,不阻塞读事务,写事务 会阻塞写事务和读事务,因为此时读事务阻塞了写事务,所以避免了“不可 重复读”的问题,但是此时读事务并没有阻塞对数据库的插入操作,所以此 时“幻影读”问题照样存在。
Serializable 隔离级别是最严格一种隔离级别,数据库系统会保证执行 此种隔离级别事务的效果和顺序执行的效果一致。不过在日常的开发过程种 很少使用此种隔离级别,因为它严重影响了系统的性能。
以上就是事务的四种隔离级别,但是实际的开发当中,我们还是需要注意以下几个问题:
1 并不是所有的数据库都支持以上的四种隔离级别,具体的数据库支持的隔离级别可参考相应的数据库文档。
2 在实际的开发当中,我们经常还会遇到一个问题就是如何在事务隔离级别和系统的并发性方面取得一个折中,如果采用 serializable 隔离级别,这样数据库就做好了并发控制,但是系统性能非常差,此时我们一般采用读取已提交的隔离级别,然后再结合以下几种并发控制的锁定策略:
* 乐观所
* 悲观锁
* 乐观离线锁
* 悲观离线锁
此时其实并发是由应用程序来控制的,而事务的隔离由数据库系统来管理。
首先事务模型分为好几种比如平面事务,嵌套事务等,平面事务是一个完整的不能再进行嵌套的事务,而嵌套型事务容许事务进行嵌套,事务嵌套子事务,这样主事务可以对嵌套子事务进行重试,这样增加了事务成功的总体的成功率,嵌套式事务最典型的例子就是订票。比如某人要去旅行,需要路径 A,B,C , D 四个不同的地方, A 到 B 需要订火车票, B 到 C 需要订机票, C 到 D 需要订船票,那么如果采用平面事务,只要 A,B,C,D 任意一段路程订票失败那么就订票失败了,这样明显成功率比较低,这种情况下可以采用嵌套型事务来避免平面性事务的刚性失败的缺点。
上面说了常见的事务模型的类型,下面主要说一下目前 JAVAEE 应用中,我们常见的事务,本地事务或者 resource-local 事务和全局事务或者说 JTA 事务,需要声明的是 resource-local 和 jta 事务都属于前面说的平面事务模型。在具体说这两种不同类型事务之前,首先我们来明确几个事务管理会涉及到的几个参与者:
1 资源管理器( Resource Manager) :资源管理器一般是数据库管理系统,也可以是消息队列,遗留系统等。
2 分布式事务协调者( Distributed Transaction Coordinator,DTC) 【 1 】:此功能一般是有我们所用的 JavaEE 应用服务器实现,比如 jboss,websphere,weblogic 等。这个角色只有在 JTA 事务中才会存在。
3 事务管理器 (Transaction manager) :每一个事务管理器都与相应的资源管理器所关联,它负责对分布式事务进行提交或者回滚。
4 应用程序( Application)
以上四者的关系可以用以下的图形来形象的表述:
在日常的系统开发中,我们一般都会使用数据资源(比如数据库)来对系统的状态进行保存,那么我们根据系统涉及的数据资源的多少,将事务分为 RESOURCE-LOCAL 事务或者 JTA 全局分布式事务。
RESOURCE-LOCAL 事务是指只有一个资源管理( RM) 的事务,比如 我们系统中只涉及到一个数据库,更准确点说应该是事务操作都是对同一个 数据库进行操作。
此时事务协调着和事务管理器的作用就有底层的资源管理器来实现了。比如目前我们在采用 spring 来管理事务的时候,其实 spring 并没有事务功能,它仅仅是封装了底层数据库的事务操作而已。
全局事务是涉及多个资源管理器,此时需要引入事务协调者来进行调节,此时就需要两阶段提交协议 2PC ( two phase commit) ,下面简要说一下两阶段提交协议。
第一阶段:事务协调者发送“准备提交”消息给事务所涉及的所有的事务管理器,然后事务管理器又分送此消息给相应的资源管理器,然后事务管理器又将资源管理的响应情况告诉分布式事务协调者( DTC). 只有此阶段顺利完成后(既所有的资源管理器都同意提交事务),才会进入第二阶段。
第二阶段:当第一个阶段顺利完成后,事务协调者告诉事务管理器去提交事务。
我们可以形象的用下图对分布式事务的参与者进行描述:
上图描述一种场景,一个分布式事务中需要操作两个数据库和一个 JMS 服务器,在这种场景下,在进行事务操作的时候,对于 DB 来说,首先事务管理器从支持 XA 接口的 JDBC 驱动程序中取得对应的 javax.transaction.xa.XAResource 实现类, 然后从相应的 JDBC 驱动中获取到 javax.sql.XAConnection 的实现类,其中 XAResource 对应用程序是透明的,应用程序只需要拿到 XAConnection 进行操作数据库即可,对于 JMS 过程类似,首先从支持 XA 的 JMS 消息中间件获取 javax.transaction.xa.XAResource ,然后再获取 javax.jms.XAConnection 的实现类,这里 XAResource 同样对应用程序是透明的。
1 << 精通 EJB3.0>>
2 <<Enterprise Javabean 3.0>>
3 << 面向模式的软件体系结构 >> 卷 2 ,卷 3
4 << 服务器端组件模式 >>
5 Specification: JSR-000907 Java(tm) Transaction API (JTA) Specification ("Specification"),此文档在附件,有需要的可以下载看看。