事务属性小结
在NoSql和内存数据库如此流行的今天,在谈关系型数据库的貌似有点落伍了,不过在传统软件行业和对数据一致性和安全性要求比较高的行业,关系型数据库还是比较普遍的。正好最近看到一个数据库事务相关的知识,自己在这几年的工作中用的比较多,也在事务上面犯过很多的错误,正好借这个机会整理以下。
事务的ACID属性
A(Atomicity)原子性: 在一个事务上下文里面,对数据库进行的任何操作,必须保证是原子的,也就是说要么不做,要么全部都做,不能只做一部分。比如insert一条数据和delete一条数据,不知能只做insert操作而不做delete操作
C(Consistency)一致性:在事务的处理过程中,数据库必须时刻要避免被置于不一致 (inconsistent)的状态。这意味着在事务期间,每次对数据库实施的插入、更新或删除操作 时,数据库的完整性约束(integrity constraints)都要得到保证,即使在事务还未被提交时 也必须如此。比如非空约束。
I(Isolation)隔离性:两个不同的事务相互之间是彼此隔离程度。有一种说法是事务之间是彼此隔离的,一个事务不能够读取另一个事务未提交的数据,这个不太准确,这个属于事务的隔离级别。wiki上的解释是“
The isolation property ensures that the concurrent execution of transactions results in a system state that could have been obtained if transactions are executed serially, i.e. one after the other“
D(Durability)持久性:事务一旦提交,在事务中对数据库进行的修改也就进行持久化存储了,不会由于系统故障导致提交后的数据丢失。
ACID是关系型数据库最重要的特性。
事务的隔离级别
这个和ACID中的隔离性有关,主要分为四个级别
1. 读未提交(Read UnCommited)
这个是最弱的隔离级别,不满足ACID中的隔离性的要求,大多数数据库并不提供这个支持。见下面的例子:
由于事务B读取了事务未提交的数据,一旦回滚,事务B读取的数据就是有问题的。
2. 读已提交(Read Commited)
这个级别不允许事务B读取事务A还未提交的 update操作更新后的数据,但是对于事务A的insert操作,在未提交之前,对事务B还是可见的。这就可能出现幻读。见下面的例子
这里面不会出现脏读,保证事务B读取的都是事务A update提交之后的数据。但是对于insert操作,就可能存在脏读的问题,例如下面一个例子
数据库里面存在ABC这一条记录,事务A新增加了XYZ这一条记录,如果事务A正常提交,事务B读取的结果没有问题,如果事务A回滚,那么事务B就出现的脏读,多了XYZ这一条记录。
记得曾经自己在这个上面弄了一个bug,大概的业务逻辑是:先根据批日期,查询某一天的总数据量,然后在查询明细数据,生成到文件里面,然后更新状态,这些都在一个事务里面。事务的隔离级别是默认的。当初发现查询的总数据量和生成的明细数据数据不一致,原来在事务里面,还会有其他的事务往表里面插入数据,无论是否最终提交,都会被查询出来,生成到文件里面。
3. 可重复读(Repeatable Read)
这个隔离级别对于insert的数据提交之后才对另外一个事务可见,不会存在幻读的情况。
但是事务之间还是会存在互相影响的情况,见下面的例子
事务A和事务B都是先读取Price的价格,然后在价格上面减去一定的数值,我们期望结果是70,但是实际结果可能是90,也可能是80。
4. 可序列化
事务只能顺序的读取数据,当一个事务在读取和修改数据的时候,另外一个事务只能挂起,直到正在读取和修改数据的事务提交之后,挂起的事务才能执行。
事务的隔离级别与并发性,一致性之间存在关系,隔离级别越高,并发性就越低,一致性就越高
这几种隔离级别并不是每个数据库都会提供,oracle没有提供第一种隔离级别“读未提交数据”,一般情况下,大多数数据库的默认隔离级别就是“读提交数据”。
这些隔离级别都是定义在java.sql. Connection中,在获取连接的时候我们可以进行设置,但是一般情况下,系统会用数据库默认的级别来设置。
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_SERIALIZABLE;
声明式编程中事务的属性
我们在使用spring活着ejb声明式编程的时候,还会接触到事务的传播属性,在spring的 TransactionDefinition 类里面进行定义。具体有以下几个数值
Required(需要)
Mandatory(强制必须)
RequiresNew(需要新的)
Supports(支持)
NotSupported(不支持)
Never(不用)
Required:当前方法必须要求开启事务,如果当前线程不存在事务,则开启新的事务,如果当前线程已经存在事务,就加入到当前事务。这个是经常使用的。但是要注意的就是一旦事务中某一个方法回滚,当前事务上下文里面所有的操作都回滚,考虑到下面一个例子:
methodA{ read A =100 ; If(methodB()){ A = A + 1; }else{ A = A - 1; } Update A; Commit; }
|
methodB{ read B ; B = B – 1 Update B;
If(B>0){ Commit; Return ture; }else{ Rollback Return false; } }
|
假设A和B的事务都是Required,那么当调用MethodA的时候,如果method回滚了,对A的修改也就回滚了。所以上面的代码不会达到预期的结果,也就是说A不可能修改成为99。
Required New:当前方法必须要求开启新的事务,如果当前线程已经存在事务上下文,就暂停当前事务,等到新事务结束之后,在继续恢复之前的事务。就拿上面的例子来说,methodB的对事务的修改不会影响到methodA。两个事务之间不会互相影响。经常可以用到的场景就是在业务发生异常的时候发送短消息。如果业务发生异常,业务回滚,但是由于发送段消息是新的事务,不会受到业务异常的影响。
Mondary:当前方法必须要求事务,如果当前线程不存在事务,就抛出异常,如果存在,就加入到事务里。
Support:当前方法支持事务,如果当前线程存在事务,就加入到事务中去,如果不存在,不做任何操作。
Not Support:当前方法不支持事务,如果当前线程存在事务,就挂起当前事务,执行完当前方法,恢复事务。一般情况下在查询的时候使用,如果一个方法只是查询,并且非常耗时,就可以使用Not Support,避免事务时间超长。
Never:当前方法不支持事务,如果当前线程存在事务,则抛出异常。这种用的比较少。