在了解Hibernate的LockMode之前,我们先讲一下LockMode是什么东西?其实LockMode只是在使用Hibernate 中 的session.load()加载数据时指定的模式,也叫悲观锁(模式),然而,悲观锁是为了弥补read-committed 机制的不足,从而解决non-repeatable (不可重复读)和 phantom-read (幻读)问题 ,而non-repeatable 和 phantom-read 这两个问题也只是事务并发是产生的两种问题...
看了我写的这一段后,我相信很多读者会有点懵,这就对了,看完下面的文章,再后过头来读这一段,就全都明白了。
我们知道,事务由那几个特性,四个(ACID):
1.原子性(Atomicity):
整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
2.一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
3.隔离性(Isolation)
两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。
4.持久性(Durability)
在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
由于一项操作通常会包含许多子操作,而这些子操作可能会因为硬件的损坏或其他因素产生问题,要正确实现ACID并不容易。ACID建议数据库将所有需要更新以及修改的资料一次操作完毕,但实际上并不可行。
一个支持事务(Transaction)的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。
在处理事务过程中,事务并发是不可避免的,从而会出现以下几个问题:
1.丢失更新(Lost Update)(第一类和第二类)
2.脏读(Dirty Read)
3.不可重复读(Non-repeatable Read)
4.幻读(Phantom Read)
针对并发事务出现的问题,Hibernate 采用了数据库的事务隔离机制(详细文档见Herbernate 参考文档的 java.sql.Connection ),一般有以下四种处理机制:
1.read-uncommitted
2.read-committed
3.repeatable read
4.serializable
四种机制的具体价值:
A. 只要数据库支持事务,就不可能出现第一类丢失更新
B. read-uncommitted(允许读取未提交的数据)会出现dirty read , phantom-read,non-repeatable read 问题
C. read-committed(读取已提交的数据 ,项目中一般使用这个,MySql 数据库默认是这种机制)不会出现dirty read ,因为只有另一个事务提交才会读出来结果,但仍然会出现non-repeatable和phantom-read
(所以使用read-committed 机制可用悲观锁和乐观锁来解决non-repeatable 和 phantom-read 问题)
在hibernate中的事务隔离级别的设定(使用 hibernate.connection.isolation配置,取之1,2,4,8)
1.hibernate.connection.isolation = 2 (如果不设置,默认依赖数据库本身的级别,例如MySql为2,read-committed)
2.使用悲观锁解决repeatable read 的问题(依赖于数据库的锁)
a. 使用悲观锁查找数据或者更新数据时,默认加上for update ,表示对当前事务加锁,其他事务暂时不能使用,例如 select ... for update
b. 在程序中如何加上悲观锁呢,很简单,直接在session的load方法中添加一个参数即可,例如:
Account a = (Account)session.load(Account.class, 1, LockMode.UPGRADE);
锁的模式:
a.LockMode.None 无锁的机制,Transaction 介绍时,切换到此模式
b.LockMode.read 在查询的时候hibernate会自动获取锁
c.LockMode.write insert update hibernate会自动获取锁
d.以上3种锁的模式,是hibernate内部使用的(不需要设置)
e.LockMode.UPGRADE_NOWAIT 是Oracle 支持的索的模式
3.使用乐观锁解决repeatable read 的问题
直接在实体类中添加 version 属性(数据库也会对应生成该字段,初始值为0),并在其get 方法前加 @version 注解 (这里介绍注解方式配置),则在操作过程中每更新一次改行数据则在 version 值上加1,即可在事务提交前判断该数据是否被其他事务修改过
D. repeatable read (事务执行中其他事务无法执行修改或插入操作 较安全)
E. serializable 解决事务隔离级别(顺序执行事务,不并发,实际当中很少使用)
悲观锁
在应用程序中显示地为数据资源加锁.悲观锁假定当前事务操纵数据资源时,肯定还会有其它事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源.尽管悲观锁能防止丢失更新和不可重复读这类并发问题,但会影响并发性能.(简单理解,就是每次在操作数据时总是悲观地认为会有别的事务也会来操纵同一数据,从此锁住该笔数据,直到自己操作完成后再解除锁)
乐观锁
假定当前事务操纵数据资源时,不会有其它事务同时访问该数据资源,因此完全依靠数据库的隔离级别来自动管理锁的工作.应用程序采用版本控制手段来避免可能出现的并发问题.(所谓乐观锁,它通常认为多个事务同时操纵同一数据的情况是很少的,因为根本不做数据库层次上的锁定,只是基于数据的版本标识实现应用程序级别上的锁定机制,既保证了多个事务的并发访问,又有效地防止了第二类丢失更新的出现)
LockMode类表示的几种锁定模式
锁定模式 | 描述 |
LockMode.NONE | 如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,默认值. |
LockMode.READ | 不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象版本一致 |
LockMode.UPGRADE | 不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象的版本一致,如果数据库系统支持悲观锁(如Oracle/MySQL),就执行select...for update语句,如果不支持(如Sybase),执行普通select语句 |
LockMode.UPGRADE_NOWAIT | 和LockMode.UPGRADE具有同样功能,此外,对于Oracle等支持update nowait的数据库,执行select...for update nowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其它事务释放锁,而是立刻抛出锁定异常 |
LockMode.WRITE | 保存对象时会自动使用这种锁定模式,仅供Hibernate内部使用,应用程序中不应该使用它 |
LockMode.FORCE | 强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象 |
多个事务并发运行时的并发问题
第一类丢失更新:撤销一个事务时,把其它事务已提交的更新数据覆盖.
第二类丢失更新:不可重复读中的特例,一个事务覆盖另一事务已提交的更新数据.
脏读:一个事务读到另一事务未提交的更新数据.
幻读:一个事务读到另一事务已提交的新插入的数据.
不可重复读:一个事务读到另一个事物已提交的更新数据.
锁的类型和兼容性
共享锁
l 加锁条件:当一个事务执行select语句时,数据库系统会为这个事务分配一把共享锁,锁定被查询的数据.
l 解锁条件:数据被读取后,数据库系统立即解除共享锁.
l 与其它锁的兼容性:如果数据资源上放置了共享锁,还能再放置共享锁和更新锁.
l 并发性能:良好的并发性能.当多个事务读相同数据时,每个事务都会获得一把共享锁,可以同时读锁定的数据.
独占锁
l 加锁条件:当一个事务执行insert,update,delete时,数据库系统会自动对SQL语句操纵的数据资源使用独占锁.如果该数据资源已经有其它锁存在时,无法对其再放置独占锁.
l 解锁条件:独占锁一直到事务结束后才能被解除.
l 与其它锁的兼容性:独占锁不能和其他锁兼容,如果数据资源已经加上了独占锁, 就不能再放置其它锁,同样,如果已经有了其它锁,就不能放置独占锁.
l 并发性能:并发性能较差,只允许有一个事务访问锁定的数据,如果其他事务也需要访问该数据,就必须等待,直到前一个事务结束,解除了独占锁,其它事务才能访问该数据.
更新锁
l 加锁条件:当一个事务进行update操作时,数据库系统会先为事务分配一把更新锁.
l 解锁条件:当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁.
l 与其它锁的兼容性:更新锁与共享锁兼容,即一个资源可以同时放置更新锁和共享锁,但是最多只能放置一把更新锁,这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其它事务必须等到前一个事务结束后,才能获得更新锁,避免了死锁.
l 并发性能:允许多个事务同时读锁定资源,但不允许其它事务修改它.
各种隔离级别所能避免的并发问题
隔离级别 | 是否出现第一类丢失更新 | 是否出现第二类丢失更新 | 是否出现脏读 | 是否出现幻读 | 是否出现不可重复读 |
Serializable串行化 | 否 | 否 | 否 | 否 | 否 |
RepeatableRead可重复读 | 否 | 否 | 是 | 否 | 否 |
ReadCommited读已提交数据 | 否 | 否 | 是 | 是 | 是 |
ReadUncommited读未提交数据 | 否 | 是 | 是 | 是 | 是 |