2019独角兽企业重金招聘Python工程师标准>>>
乐观锁:
原理:
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,说明这里在数据库中加了一把锁。