一、事物并发三种常见问题
1.dirty read 脏读
时间 | 事物A | 事物B |
---|---|---|
T1 | 开始事物 | 开始事物 |
T2 | 查询账户余额为1000 | |
T3 | 汇入100把余额变为1100 | |
T4 | 读取账户余额为1100 | |
T5 | 回滚 | |
T6 | 取款1100 | |
T7 | 提交事物失败 |
事物A读到了事物B还没有提交的数据
2.non-repeatable read 不可重复读
时间 | 事物A | 事物B |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000 | |
T4 | 汇入100把余额变为1100 | |
T5 | 提交事物 | |
T6 | ||
T7 | 查询余额为1100 | |
T8 | 提交事物 |
事物A第一次读余额为1000,B在中间提交了一次事物把余额改为了1100,事物A再读取余额变为了1100。读取同一个数据得到了了两个不同的结果
3.phantom read 幻读
时间 | 事物A | 事物B |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询学生数为10人 | |
T4 | 插入一个学生 | |
T5 | 查询学生数为11人 | |
T6 | 提交事物 | |
T7 | 提交事物 |
事务A执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,是事务B在这两次查询中间插入或者删除了数据造成的。
不可重复读和幻读的区别在与一个是前者强调更新,后者强调插入和删除。
二、事物隔离机制
1.read uncommitted 可读未提交
2.read committed 可读已提交
3.repeatable read 可重复读
4.serializable 串行化
隔离级别 | 是否脏读 | 是否不可重复读 | 是否幻读 |
---|---|---|---|
read uncommitted | 是 | 是 | 是 |
read committed | 否 | 是 | 是 |
repeatable read | 否 | 否 | 是 |
serializable | 否 | 否 | 否 |
隔离级别越高,效率越低。
一般把事物级别设为2:read committed
如果不设置事物级别,则根据数据库的默认级别来设置,如MySQL默认级别是repeatable read
设定Hibernate的事物隔离机级别
1:read uncommitted;2:read committed;4:repeatable read;8:serializable
<property name="hibernate.connection.isolation">2</property>
三、乐观锁和悲观锁
当事务隔离级别设为2(read committed)时,任会出现non-repeatable read和phantom read(很少出现,可以忽略)问题。因此我们需要乐观锁或悲观锁来解决non-repeatable read 问题。
悲观锁
悲观锁假定当前事务操纵数据资源时,可能会有其他事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源。尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它影响并发性能,因此应该很谨慎地使用悲观锁。
select … for update
Hibernate3
User u =(User)session.get(User.class,1,LockMode.UPGRADE);
在Hibernate3.6以上的版本中”LockMode”已经不建议使用
Hibernate4
User u =(User)session.get(User.class,1,LockOptions.UPGRADE);
在读取数据时,借助数据库为这条数据上锁,即在sql语句后加上 for update,在这个事物提交前,其他事物无法对这条数据进行改动。
乐观锁
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。
private int version;
@Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
在表里加上一个version字段,记录当前的版本号,更新数据时version会加1。将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则更新,否则就报错。
比如
事物A取出一条记录,当前版本号为0,更改记录后版本号增加1。如果在事物A提交前有个事物B更新了该数据,数据库内的版本号变为了1,A再提交时就会报错。