潜在的事务隔离问题
在事务中可能存在的潜在问题可以被归纳为这三类:脏读(Dirty reads)、非可重复性读取(Nonrepeatable reads)和幻读(Phantoms)。
● 脏读——脏读的意思是你可以从一个事务外部读取事务内部被改变的数据。假设事务一改变了一些客户信息,但是这个事务失败了,所以在回滚后数据并没有改变。如果同时事务二也在读取这些客户记录,则读取出来的数据实际上并不存在,这就是脏读。
● 非可重复性读取——如果同一行的数被读取多次,你每次得到的值都不相同,则被称为非可重复性读取。比如你在事务一内读取数据,而与此同时事务二改变了你在事务一中读取的那些数据,然后你在事务一中再次读取了该数据,这样同一个事务内多次读取相同数据的值却不同。这就是非可重复性读取。
● 幻读——幻读的意思是某个记录满足搜索条件,但没有出现在查询结果内。比如你在事务一内按照某种查询条件查询数据,而与此同时事务二插入了一条新的记录,并且此记录也能满足事务一中的查询条件,则这条记录行就属于幻读。譬如所有满足事务一内查询的纪录都被更新了(譬如,所有薪水小于$1000的雇员的薪水都被调整到$1000),在事务一结束后,在事务二新添加的这条记录未被更新。
Transaction Isolation Levels
事务隔离级别
取决于设定的隔离级别,这些问题中有些可能会发生,或者隔离级别保证了这些问题都不会发生。你可以为隔离级别设定这些值:可读取未提交数据(read uncommitted)、
可读取提交数据(read committed)、可重复读取(repeatable read)和序列化(serializable)。
● 可读取未提交数据——如果配置为这个级别,事务并没有互相隔离开来。对于事务来讲这是最有扩缩性的的方式,但是前面提到的所有问题都有可能发生。
● 可读取提交数据——在这个级别,事务必须等待所有在它操作范围内那些带有写入锁的记录解锁。这样就保证了脏读的情况不会发生。事务对所有的只读行持有一个读取锁,对所有更新或者删除的行持有一个写入锁。在移动到下一行后读取锁会被解开,而所有的写入锁都将被维持直到事务被完成或者中止为止。
● 可重复性读取——可重复性读取在原理上和可读取提交数据相似。唯一的不同处在于,读取锁会一直被保持到事务被完成或中止为止。这样,非可重复性读取的问题将不存在。
● 序列化——与可读取提交数据和可重复性读取类似,事务必须等待所有在它操作范围内那些带有写入锁的记录解锁。与可重复性读取类似,读取锁一直作用于只读的行上,写入锁一直作用于更新或者删除的行上,区别在于被锁定数据的范围。如果执行查询语句SELECT * FROM Customers,则表Customers会被锁定。这样没有新的纪录可以被插入。如果查询语句是SELECT * FROM Customers WHERE City = ‘Vienna’,则所有City列值为Vienna的行都被锁定了。这样,幻读行不可能出现。表7-5总结了各个隔离级别下可能发生的问题。
When to Use Which Isolation Level
什么时候使用什么隔离级别
一致性和并发能力是相互冲突的。为了保持数据的一致性,在访问数据时必须加锁。为了最严格的一致性,序列化级隔离提供了最好的支持。而锁定数据同样意味着别的作业无法同时访问该数据。为了得到更好的并发操作的能力,你得选择一个低一点的隔离级别。
表7-5 隔离级别行为
隔离级别 | 可读取未提交数据 | 可读取提交数据 | 可重复性读取 | 序列化 |
脏读 | Yes | No | No | No |
非可重复性读取 | Yes | Yes | No | No |
幻读 | Yes | Yes | Yes | No |
根据具体要做的事情,你可以选择一个合适的隔离级别。如果仅仅是要插入一些新的记录,那你可以设置隔离级别为读取未提交数据。如果数据会被读取一次或多次,并且如果不要求严格保持一致性(比如你正在建立一个管理报告),你可以设置为可读取提交数据。
考虑如何配置隔离级别时,需要先考虑整个保护的任务在任务进行中会发生什么事情,这些事情会怎样影响到任务的执行。有了这些信息,你可以选择出需要的隔离级别。
Specifying the Isolation Level
指定隔离级别
通过给[Transaction]特性的Isolation属性赋一个TransactionIsolationLevel枚举值,你可以设定一个服务组件的事务隔离级别。
[Transaction(TransactionOption.Required,
Isolation = TransactionIsolationLevel.Any)]
[EventTrackingEnabled]
public class CustomerData : ServicedComponent
表7-6展示了TransactionIsolationLevel各个枚举值和其含义。
表7-6 TransactionIsolationLevel枚举
TransactionIsolationLevel值
描述
Any
如果你设置隔离级别为Any,则使用和调用者相同的隔离级别。如果该对象已经是根对象,则使用的隔离级别是Serializable。
ReadUncommitted
使用ReadUncommitted时,仅使用共享锁,独占锁不会被允准。假如你只是需要读取一些数据结果,但是不需要这些数据同步到以秒为计的量级(与数据库完全同步),你应该使用这个选项。
ReadCommitted
使用这个选项,当数据在被读取时使用了共享锁。读完数据后,这个共享锁就会被释放。在事务完成之前,数据可以被改变。
RepeatableRead
如果设置为RepeatableRead,则所有被使用的数据上都会被加上锁。
Serializable
序列级有着最好的隔离效果。在这个选项的作用下,更新或插入在相同范围的数据是被禁止的。
如果使用无组件事务,你可以通过设定ServiceConfig类的Isolation属性来指定需要的事务隔离级别。