乐观锁和悲观锁

乐观锁:

原理:
	1)通过在数据库表中添加一个版本号(version)字段来实现乐观锁。
	2)更新前先获取到该条数据的版本号(v1),然后在更新语句(更新数据&更新版本号)的where条件中添加 version=v1 条件,
		1>若满足version=v1条件(即:成功获取乐观锁),则成功更新数据且版本号+1;
		2>若不满足version=v1条件(即:获取乐观锁失败),说明该条数据被其它线程修改过了,则更新失败,回滚事务。
特点:
	1)不发生获取锁失败的情况下,开销比较小。
	2)若获取锁失败,则代码需要回滚,开销比较大。
	
应用:
	乐观锁适用于锁获取失败的概率比较小的场景,即:读取比较频繁、写入较少的场景。

缺点:
	只能保证本系统对数据(数据库表)的操作是安全的,外部系统对数据(数据库表)的操作是不可控的。
	解决办法:对外部系统设置权限,即外部系统只有普通查询的权限。

悲观锁:

原理:使用数据库提供的锁机制(select .. for update)。

注意:使用悲观锁前,必须先关闭MySQL的自动提交属性。

应用:
	写入比较频繁的场景。

我们应该尽量避免使用长事务:

1)在一个事务中执行批量操作(eg:循环插入数据、循环删除数据等)会导致该事务的执行时间变长。
2)长事务会导致数据库连接被长时间持有,如果该请求的并发量较高,则很可能出现连接池中的连接被用光的情况,从而导致其它的请求(因无法获取到数据库连接)一直处于等待状态,无法被响应。
3)我们应该将事务的范围控制在单个操作上。

Hibernate中的乐观锁和悲观锁:

概念:指数据库的隔离级别设为read committed时,为了解决不可重复读的问题而采用的两种办法:

1)设定hibernate的事务隔离级别(使用hibernate.connection.isolation配置:取值1、2、4、8)
	hibernate.connection.isolation = 2(如果不设,则默认依赖数据库本身的级别)
2)采用乐观锁和悲观锁解决不可重复读的问题
	1)悲观锁:使用另一种load方法:load(xx.class , id , LockMode.Upgrade),把读出来的数据加上一把锁,在事务结束前别人无法访问,需要借助数据库中的锁。
		注:LockMode.UPGRADE_NOWAIT是 ORACLE 支持的锁的方式
	2)乐观锁:在程序中添加一个version字段,用来检查是否被修改过。版本检查使用版本号或者时间戳来检测更新冲突(并且防止更新丢失)。
		在实体类中增加version属性(数据库也会对应生成该字段,初始值为0),并在其get方法前加@Version注解,则在操作过程中每更新一次该行数据则version值加1,即可在事务提交前判断该数据是否被其他事务修改过。

eg:
	悲观锁(PessimisticLock):

	public void testPessimisticLock() {
		Session session = sf.openSession();
		session.beginTransaction();
		
		// LockMode.UPGRADE 的意思就是:在读这条记录的时候,请数据库为我读的这条记录加把锁
		Account a = (Account)session.load(Account.class, 1L, LockMode.UPGRADE);
		int balance = a.getBalance();
		//do some caculation
		balance = balance - 10;
		a.setBalance(balance);
		
		session.getTransaction().commit();
		session.close();
	}

	控制台发出的SQL语句:select ... for update	
	分析:在select语句后加上了for update,说明这里在数据库中加了一把锁。

猜你喜欢

转载自my.oschina.net/u/1399755/blog/1797008