Hibernate的悲观锁和乐观锁

问题:
首先服务器结构是这样的。
   1,多台tomcat集群
   2,一台数据库服务器

现在问题是:
  网站上一个功能,点击的时候需要先查询这条数据的状态 如果状态为0那么就可以修改这条数据状态为1 如果状态是1 则不做操作 , 现在这条数据状态是0, 我开浏览器2个窗口 同时点击这一功能,正常的流程应该是 第一个请求 修改了状态为1, 第二个请求不做操作,但是结果是两个请求都修改了状态为1(根据输出SQL), 由于使用分布式,所以无法使用 同步锁 解决,请问有什么解决方案? 如果用什么技术解决?


解决:

1. 使用悲观锁 行级锁
2. 使用存储过程完成查询 修改操作



http://blog.csdn.net/fengxuezhiye/article/details/7380076

http://blog.csdn.net/cutesource/article/details/5791350


在分布式环境中,处理并发问题就没办法通过操作系统和JVM的工具来解决,那么在分布式环境中,可以采取一下策略和方式来处理:

  • 避免并发
  • 时间戳
  • 串行化
  • 数据库
  • 行锁
  • 统一触发途径

避免并发

在分布式环境中,如果存在并发问题,那么很难通过技术去解决,或者解决的代价很大,所以我们首先要想想是不是可以通过某些策略和业务设计来避免并发。比如通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,比如在以前博文中提到的例子,我们需要用多线程或分布式集群来计算一堆客户的相关统计值,由于客户的统计值是共享数据,因此会有并发潜在可能。但从业务上我们可以分析出客户与客户之间 数据是不共享的,因此可以设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去 完成。这种规则很容易设计,例如可以采用hash算法。

时间戳

分布式环境中并发是没法保证时序的,无论是通过远程接口的同步调用或异步消息,因此很容易造成某些对时序性有要求的业务在高并发时产生错误。比如系统A需要把某个值的变更同步到系统B,由于通知的时序问题会导致一个过期的值覆盖了有效值。对于这个问题,常用的办法就是采用时间戳的方式,每次系统A发送变更给系统B的时候需要带上一个能标示时序的时间戳,系统B接到通知后会拿时间戳与存在的时间戳比较,只有当通知的时间戳大于存在的时间戳,才做更新。这种方式比较简单,但关键在于调用方一般要保证时间戳的时序有效性。

串行化

有的时候可以通过串行化可能产生并发问题操作,牺牲性能和扩展性,来满足对数据一致性的要求。比如分布式消息系统就没法保证消息的有序性,但可以通过变分布式消息系统为单一系统就可以保证消息的有序性了。另外,当接收方没法处理调用有序性,可以通过一个队列先把调用信息缓存起来,然后再串行地处理这些调用。

数据库

分布式环境中的共享资源不能通过Java里同步方法或加锁来保证线程安全,但数据库是分布式各服务器的共享点,可以通过数据库的高可靠一致性机制来满足需求。比如,可以通过唯一性索引来解决并发过程中重复数据的生产或重复任务的执行;另外有些更新计算操作也尽量通过sql来完成,因为在程序段计算好后再去更新就有可能发生脏复写问题,但通过一条sql来完成计算和更新就可以通过数据库的锁机制来保证update操作的一致性。

行锁

有的事务比较复杂,无法通过一条sql解决问题,并且有存在并发问题,这时就需要通过行锁来解决,一般行锁可以通过以下方式来实现:

  • 对于Oracle数据库,可以采用select ... for update方式。这种方式会有潜在的危险,就是如果没有commit就会造成这行数据被锁住,其他有涉及到这行数据的任务都会被挂起,应该谨慎使用
  • 在表里添加一个标示锁的字段,每次操作前,先通过update这个锁字段来完成类似竞争锁的操作,操作完成后在update锁字段复位,标示已归还锁。这种方式比较安全,不好的地方在于这些update锁字段的操作就是额外的性能消耗

统一触发途径

当一个数据可能会被多个触发点或多个业务涉及到,就有并发问题产生的隐患,因此可以通过前期架构和业务设计,尽量统一触发途径,触发途径少了一是减少并发的可能,也有利于对于并发问题的分析和判断。





谈到悲观锁和乐观锁,就要谈到数据库的并发问题,数据库的隔离级别越高并发性就越差
并发性:
当前系统进行了序列化后,你读取数据库后,别人查询不了,称为并发性不好

1.悲观锁

具有排它性(我锁住当前数据后,比人看不到此数据),悲观锁一般是由数据库机制来做到的

悲观锁的实现:

通常依赖于数据库机制,在整修过程中将数据库锁定,其它任何用户都不能读取或修改

悲观锁的适用场景:

悲观锁一般适合短事物比较多(如某一个数据取出后加1,立即释放)


实例:

Hibernate的悲观锁和乐观锁_第1张图片

用户1,用户2同时读取到数据,但是用户2先-200,这时数据库里的是800,现在用户1也开始-200,可以用户1刚才读取到的数据是1000,现在用户用刚一开始读取的数据1000-200,而用户1在更新时数据库里的数据是800,按理说用户1应该是800-200=600,这样就造成更新丢失。这种情况下可采用两种方式解决:悲观锁、乐观锁。
悲观锁:用户1读取数据后,用锁将其读取的数据锁上,这时用户2是读取不到数据的,只有用户1释放锁后用户2才可以读取,同样用户2读取数据的数据也锁上,这样就可以解决更新丢失了。

实体类:

[html] view plain copy print ?
  1. public class Inventory {  
  2.     private int itemNo;   
  3.     private String itemName;      
  4.     private int quantity;  
  5.     public int getItemNo() {  
  6.         return itemNo;  
  7.     }  
  8.     public void setItemNo(int itemNo) {  
  9.         this.itemNo = itemNo;  
  10.     }  
  11.     public String getItemName() {  
  12.         return itemName;  
  13.     }  
  14.     public void setItemName(String itemName) {  
  15.         this.itemName = itemName;  
  16.     }  
  17.     public int getQuantity() {  
  18.         return quantity;  
  19.     }  
  20.     public void setQuantity(int quantity) {  
  21.         this.quantity = quantity;  
  22.     }     
  23. }  

映射文件:

[html] view plain copy print ?
  1. <hibernate-mapping>  
  2.     <class name="com.cn.hibernate.Inventory" table="t_inventory">  
  3.         <id name="itemNo">  
  4.             <generator class="native"/>  
  5.         </id>  
  6.         <property name="itemName"/>  
  7.         <property name="quantity"/>  
  8.     </class>  
  9. </hibernate-mapping>  

悲观锁的使用:

如果要使用悲观锁,肯定在加载数据时就要锁住,通常采用for update语句

Hibernate使用load进行悲观锁加载


Session.load(Class arg(),Serializable arg1,LockMode arg2)throws HibernateException

LockMode:悲观锁模式(一般使用LockMode.UPGRADE)

[html] view plain copy print ?
  1. session = HibernateUtils.getSession();  
  2.             tx = session.beginTransaction();  
  3.             Inventory inv = (Inventory)session.load(Inventory.class, 1, LockMode.UPGRADE);  
  4.             System.out.println(inv.getItemName());  
  5.             inv.setQuantity(inv.getQuantity()-200);  
  6.               
  7.             session.update(inv);  
  8.             tx.commit();  
如果使用悲观锁,那么lazy(懒加载)无效


2.乐观锁

乐观锁:不是锁,是一种冲突检测机制,乐观锁的并发性较好,因为我改的时候,别人可随便修改乐观锁的实现方式:常用的是版本的方式(每个数据表中有一个版本字段version,某一个用户更新数据库后,版本号+1,另一个用户修改后再+1,当用户更新发现数据库当前版本号与读取数据时版本号不一致,等于或小于数据库版本号则更新不了)

Hibernate使用乐观锁需要在映射文件中配置才可生效

实体类

[html] view plain copy print ?
  1. <pre class="html" name="code">public class Inventory {  
  2.     private int itemNo;   
  3.     private String itemName;      
  4.     private int quantity;     
  5.     private int version;//<span style="color: rgb(255, 0, 0);">Hibernate用户实现版本方式乐观锁,但需要在映射文件中配置</span>  
  6.     public int getItemNo() {  
  7.         return itemNo;  
  8.     }  
  9.     public void setItemNo(int itemNo) {  
  10.         this.itemNo = itemNo;  
  11.     }  
  12.     public String getItemName() {  
  13.         return itemName;  
  14.     }  
  15.     public void setItemName(String itemName) {  
  16.         this.itemName = itemName;  
  17.     }  
  18.     public int getQuantity() {  
  19.         return quantity;  
  20.     }  
  21.     public void setQuantity(int quantity) {  
  22.         this.quantity = quantity;  
  23.     }  
  24.     public int getVersion() {  
  25.         return version;  
  26.     }  
  27.     public void setVersion(int version) {  
  28.         this.version = version;  
  29.     }  
  30. }  
  31. </pre>  
  32. <pre></pre>  
  33. <p></p>  
  34. <pre></pre>  
  35. <pre></pre>  
  36. <pre></pre>  
  37. <pre></pre>  
  38.       
  39.         <div style="padding-top: 20px;">           
  40.             <p style="font-size: 12px;">版权声明:本文为博主原创文章,未经博主允许不得转载。</p>  
  41.         </div>  
 
 

 
 
 
 
 
 
 
 

版权声明:本文为博主原创文章,未经博主允许不得转载。

 
 
    



你可能感兴趣的:(数据库,Hibernate,乐观锁,悲观锁)