自从有了并发,原有的程序哪儿哪儿都不好了——小白语录。
并发事务所导致的问题:
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题
并发事务所导致的问题可以分为下面三种类型:
脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
(本文出自:http://my.oschina.net/happyBKs/blog/513441)
事务的隔离级别
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行.
在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
事务的隔离级别可以通过隔离事务属性指定
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
Mysql 支持 4 种事务隔离级别.
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
设置隔离事务属性
用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别.
在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定隔离级别
设置回滚事务属性
默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.
rollbackFor: 遇到时必须进行回滚
noRollbackFor: 一组异常类,遇到时必须不回滚
在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定回滚规则. 如果有不止一种异常, 用逗号分隔.
设置超时和只读事务属性
超时和只读属性可以在 @Transactional 注解中定义.超时属性以秒为单位来计算.
在 Spring 2.x 事务通知中, 超时和只读属性可以在 <tx:method> 元素中进行指定.
我们在昨天的例子中将买东西事务的方法进行了相关设置,包括事务隔离级别、只读属性、超时timeout
package com.happBKs.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//使用propagation指定事务的传播行为,即当前事务的方法被另一个事务方法调用时,如何使用是事务。
//使用另外一个方法的事务(REQUIRED)?还是另开一个新事物?(REQUIRES_NEW)
//默认REQUIRED
//1. REQUIRES_NEW:事务自己的事务,调用该事物方法的事务被挂起
//
//2. 用propagation指定事务的隔离级别,最常用的是READ_COMMITTED
//3. 使用readOnly指定事务是否为只读,表示这个事务只读取数据,不更新数据,这样可以帮助数据库引擎优化事务。
//如果真的是一个只读取数据库值的方法,应该设置readOnly=true
//4. 使用timeout设置一个时间限额,单位为秒,如果事务的执行时间超过这个时间阈值,事务将被强制回滚。
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=5
)
public void purchase(String username, String isbn) {
// TODO Auto-generated method stub
//1. 获取书的单价
int price=bookShopDao.findBookPriceIsdn(isbn);
//2. 更新书的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}