1、数据库中的乐观锁:
乐观锁对于数据是处于乐观的态度,认为一般数据不会发生冲突,所以在数据提交的时候才会去正式对数据是否发生冲突进行检查,如果发生冲突则会返回错误给用户,让用户去抉择。
乐观锁的实现(如果有人在你之前更新了,你的更新是被拒绝得,用户可以重新操作,大多数基于数据版本记录机制实现):
可以给数据加一个version字段,字段内容可以是时间戳或者是版本号,第一次读取的时候连version一起读取出来,数据更新一次就version+1,当我们提交更新的时候,判断当前版本号和第一次取出来的版本号做对比,如果版本号一样则更新,否则认为是过期数据,拒绝更新,让用户重新操作。
2、数据库中的悲观锁:
悲观锁对于数据是处于悲观的态度,认为数据一定会发生冲突,所以在对数据的读取的时候就会把数据锁定。(数据暂时不会得到修改)
悲观锁的实现(大多数情况通过数据库锁机制实现):
排他锁:
当数据操作时把这部分数据锁定,知道操作完成之后才会解锁,其他事务才能操作这部分数据。
一般使用select.........for update进行对数据进行加锁处理,例如:select * from account where name='MAX' for update;这条语句锁定了数据库中符合条件的这部分数据,事务提交之前外界无法修改这部分数据。
3、悲观锁和乐观锁在实际中的应用:
悲观锁和乐观锁只是数据库中的两种思想,具体的实现需要sql语句、数据的设计和代码来实现。
3.1 hibernate的悲观锁
基于数据库的锁机制实现。如下查询语句:
- String hqlStr ="from TUser as user where user.name=Max";
- Query query = session.createQuery(hqlStr);
- query.setLockMode("user",LockMode.UPGRADE); //加锁
- List userList = query.list();//执行查询,获取数据
- select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里Hibernate通过使用数据库的for update子句实现了悲观锁机制。对返回的所有user记录进行加锁。
2、Hibernate的加锁模式有:
Ø LockMode.NONE : 无锁机制。
Ø LockMode.WRITE :Hibernate在写操作(Insert和Update)时会自动获取写锁。
Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。
这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。
Ø LockMode.UPGRADE :利用数据库的for update子句加锁。
Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for update nowait子句实现加锁。
注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。
3、Hibernate的乐观锁
Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的生产力。Hibernate中可以通过class描述符的optimistic-lock属性结合version描述符指定。具体实现方式如下:
现在,我们为之前示例中的TUser加上乐观锁机制。
实现一、 配置optimistic-lock属性:
- <hibernate-mapping>
- <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">
- ……
- </class>
- </hibernate-mapping>
Ø none: 无乐观锁
Ø version: 通过版本机制实现乐观锁
Ø dirty: 通过检查发生变动过的属性实现乐观锁
Ø all: 通过检查所有属性实现乐观锁
通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。
实现二、添加一个Version属性描述符
- <hibernate-mapping>
- <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">
- <id name="id" column="id" type="java.lang.Integer">
- <generator class="native"/>
- </id>
- <version column="version" name="version" type="java.lang.Integer"/>
- ……
- </class>
- </hibernate-mapping>
测试:
此时如果我们尝试编写一段代码,更新TUser表中记录数据,如:
- Criteria criteria = session.createCriteria(TUser.class);
- criteria.add(Expression.eq("name","Max"));
- List userList = criteria.list();
- TUser user =(TUser)userList.get(0);
- Transaction tx = session.beginTransaction();
- user.setUserType(1); //更新UserType字段
- tx.commit();
- Session session= getSession();
- Criteria criteria = session.createCriteria(TUser.class);
- criteria.add(Expression.eq("name","Max"));
- Session session2 = getSession();
- Criteria criteria2 = session2.createCriteria(TUser.class);
- criteria2.add(Expression.eq("name","Max"));
- List userList = criteria.list();
- List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
- TUser user2 =(TUser)userList2.get(0);
- Transaction tx = session.beginTransaction();
- Transaction tx2 = session2.beginTransaction();
- user2.setUserType(99);
- tx2.commit();
- user.setUserType(1);
- tx.commit();
这就是hibernate实现悲观锁和乐观锁的主要方式。
四、总结悲观锁相对比较谨慎,设想现实情况应该很容易就发生冲突,所以我还是独占数据资源吧。
乐观锁就想得开而且非常聪明,应该是不会有什么冲突的,我对表使用一个时间戳或者版本号,每次读、更新操作都对这个字段进行比对,如果在我之前已经有人对数据进行更新了,那就让它更新,大不了我再读一次或者再更新一次。
乐观锁的管理跟SVN管理代码版本的原理很像,如果在我提交代码之前用本地代码的版本号与服务器做对比,如果本地版本号小于服务器上最新版本号,则提交失败,产生冲突代码,让用户决定选择哪个版本继续使用。在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法 另外,Mysql在处理并发访问数据上,还有添加读锁(共享锁)、写锁(排它锁),控制锁粒度【表锁(table lock)、行级锁(row lock)】等实现,有兴趣可以继续研究。