一、为什么要使用锁?
要想弄清楚锁机制存在的原因,首先要了解事务的概念。
事务是对数据库一系列相关的操作,它必须具备ACID特征:
A(原子性):要么全部成功,要么全部撤销。
C(一致性):要保持数据库的一致性。
I(隔离性):不同事务操作相同数据时,要有各自的数据空间。
D(持久性):一旦事务成功结束,它对数据库所做的更新必须永久保持。
我们常用的关系型数据库RDBMS实现了事务的这些特性。其中,原子性、
一致性和持久性都是采用日志来保证的。而隔离性就是由今天我们关注的
锁机制来实现的,这就是为什么我们需要锁机制。
如果没有锁,对隔离性不加控制,可能会造成哪些后果呢?
1.更新丢失:事务1提交的数据被事务2覆盖。
2.脏读:事务2查询到了事务1未提交的数据。
3.虚读:事务2查询到了事务1提交的新建数据。
4.不可重复读:事务2查询到了事务1提交的更新数据。
下面来看Hibernate的例子,两个线程分别开启两个事务操作tb_account表中
的同一行数据col_id=1。
package com.cdai.orm.hibernate.annotation;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "tb_account")
public class Account implements Serializable {
private static final long serialVersionUID = 5018821760412231859L;
@Id
@Column(name = "col_id")
private long id;
@Column(name = "col_balance")
private long balance;
public Account() {
}
public Account(long id, long balance) {
this.id = id;
this.balance = balance;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getBalance() {
return balance;
}
public void setBalance(long balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", balance=" + balance + "]";
}
}
package com.cdai.orm.hibernate.transaction;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import com.cdai.orm.hibernate.annotation.Account;
public class DirtyRead {
public static void main(String[] args) {
final SessionFactory sessionFactory = new AnnotationConfiguration().
addFile("hibernate/hibernate.cfg.xml").
configure().
addPackage("com.cdai.orm.hibernate.annotation").
addAnnotatedClass(Account.class).
buildSessionFactory();
Thread t1 = new Thread() {
@Override
public void run() {
Session session1 = sessionFactory.openSession();
Transaction tx1 = null;
try {
tx1 = session1.beginTransaction();
System.out.println("T1 - Begin trasaction");
Thread.sleep(500);
Account account = (Account)
session1.get(Account.class, new Long(1));
System.out.println("T1 - balance=" + account.getBalance());
Thread.sleep(500);
account.setBalance(account.getBalance() + 100);
System.out.println("T1 - Change balance:" + account.getBalance());
tx1.commit();
System.out.println("T1 - Commit transaction");
Thread.sleep(500);
}
catch (Exception e) {
e.printStackTrace();
if (tx1 != null)
tx1.rollback();
}
finally {
session1.close();
}
}
};
// 3.Run transaction 2
Thread t2 = new Thread() {
@Override
public void run() {
Session session2 = sessionFactory.openSession();
Transaction tx2 = null;
try {
tx2 = session2.beginTransaction();
System.out.println("T2 - Begin trasaction");
Thread.sleep(500);
Account account = (Account)
session2.get(Account.class, new Long(1));
System.out.println("T2 - balance=" + account.getBalance());
Thread.sleep(500);
account.setBalance(account.getBalance() - 100);
System.out.println("T2 - Change balance:" + account.getBalance());
tx2.commit();
System.out.println("T2 - Commit transaction");
Thread.sleep(500);
}
catch (Exception e) {
e.printStackTrace();
if (tx2 != null)
tx2.rollback();
}
finally {
session2.close();
}
}
};
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
}
System.out.println("Both T1 and T2 are dead.");
sessionFactory.close();
}
}
事务1将col_balance减小100,而事务2将其减少100,最终结果可能是0,也
可能是200,事务1或2的更新可能会丢失。log输出也印证了这一点,事务1和2
的log交叉打印。
T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=?
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=?
T1 - balance=100
T2 - balance=100
T2 - Change balance:0
T1 - Change balance:200
Hibernate: update tb_account set col_balance=? where col_id=?
Hibernate: update tb_account set col_balance=? where col_id=?
T1 - Commit transaction
T2 - Commit transaction
Both T1 and T2 are dead.
由此可见,隔离性是一个需要慎重考虑的问题,理解锁很有必要。
二、有多少种锁?
常见的有共享锁、更新锁和独占锁。
1.共享锁:用于读数据操作,允许其他事务同时读取。当事务执行select语句时,
数据库自动为事务分配一把共享锁来锁定读取的数据。
2.独占锁:用于修改数据,其他事务不能读取也不能修改。当事务执行insert、
update和delete时,数据库会自动分配。
3.更新锁:用于避免更新操作时共享锁造成的死锁,比如事务1和2同时持有
共享锁并等待获得独占锁。当执行update时,事务先获得更新锁,然后将
更新锁升级成独占锁,这样就避免了死锁。
此外,这些锁都可以施加到数据库中不同的对象上,即这些锁可以有不同的粒度。
如数据库级锁、表级锁、页面级锁、键级锁和行级锁。
所以锁是有很多种的,这么多锁要想完全掌握灵活使用太难了,我们又不是DBA。
怎么办?还好,锁机制对于我们一般用户来说是透明的,数据库会自动添加合适的
锁,并在适当的时机自动升级、降级各种锁,真是太周到了!我们只需要做的就是
学会根据不同的业务需求,设置好隔离级别就可以了。
三、怎样设置隔离级别?
一般来说,数据库系统会提供四种事务隔离级别供用户选择:
1.Serializable(串行化):当两个事务同时操纵相同数据时,事务2只能停下来等。
2.Repeatable Read(可重复读):事务1能看到事务2新插入的数据,不能看到对
已有数据的更新。
3.Read Commited(读已提交数据):事务1能看到事务2新插入和更新的数据。
4.Read Uncommited(读未提交数据):事务1能看到事务2没有提交的插入和更新
数据。
四、应用程序中的锁
当数据库采用Read Commited隔离级别时,可以在应用程序中采用悲观锁或乐观锁。
1.悲观锁:假定当前事务操作的数据肯定还会有其他事务访问,因此悲观地在应用
程序中显式指定采用独占锁来锁定数据资源。在MySQL、Oracle中支持以下形式:
select ... for update
显式地让select采用独占锁锁定查询的记录,其他事务要查询、更新或删除这些被
锁定的数据,都要等到该事务结束后才行。
在Hibernate中,可以在load时传入LockMode.UPGRADE来采用悲观锁。修改前面的例子,
在事务1和2的get方法调用处,多传入一个LockMode参数。从log中可以看出,事务1和2
不再是交叉运行,事务2等待事务1结束后才可以读取数据,所以最终col_balance值是正确
的100。
package com.cdai.orm.hibernate.transaction;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import com.cdai.orm.hibernate.annotation.Account;
import com.cdai.orm.hibernate.annotation.AnnotationHibernate;
public class UpgradeLock {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
final SessionFactory sessionFactory = AnnotationHibernate.createSessionFactory();
// Run transaction 1
Thread t1 = new Thread() {
@Override
public void run() {
Session session1 = sessionFactory.openSession();
Transaction tx1 = null;
try {
tx1 = session1.beginTransaction();
System.out.println("T1 - Begin trasaction");
Thread.sleep(500);
Account account = (Account)
session1.get(Account.class, new Long(1), LockMode.UPGRADE);
System.out.println("T1 - balance=" + account.getBalance());
Thread.sleep(500);
account.setBalance(account.getBalance() + 100);
System.out.println("T1 - Change balance:" + account.getBalance());
tx1.commit();
System.out.println("T1 - Commit transaction");
Thread.sleep(500);
}
catch (Exception e) {
e.printStackTrace();
if (tx1 != null)
tx1.rollback();
}
finally {
session1.close();
}
}
};
// Run transaction 2
Thread t2 = new Thread() {
@Override
public void run() {
Session session2 = sessionFactory.openSession();
Transaction tx2 = null;
try {
tx2 = session2.beginTransaction();
System.out.println("T2 - Begin trasaction");
Thread.sleep(500);
Account account = (Account)
session2.get(Account.class, new Long(1), LockMode.UPGRADE);
System.out.println("T2 - balance=" + account.getBalance());
Thread.sleep(500);
account.setBalance(account.getBalance() - 100);
System.out.println("T2 - Change balance:" + account.getBalance());
tx2.commit();
System.out.println("T2 - Commit transaction");
Thread.sleep(500);
}
catch (Exception e) {
e.printStackTrace();
if (tx2 != null)
tx2.rollback();
}
finally {
session2.close();
}
}
};
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
}
System.out.println("Both T1 and T2 are dead.");
sessionFactory.close();
}
}
T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
T2 - balance=100
T2 - Change balance:0
Hibernate: update tb_account set col_balance=? where col_id=?
T2 - Commit transaction
T1 - balance=0
T1 - Change balance:100
Hibernate: update tb_account set col_balance=? where col_id=?
T1 - Commit transaction
Both T1 and T2 are dead.
Hibernate对于SQLServer 2005会执行SQL:
select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
为选定的col_id为1的数据行加上行锁和更新锁。
2.乐观锁:假定当前事务操作的数据不会有其他事务同时访问,因此完全依靠数据库
的隔离级别来自动管理锁的工作。在应用程序中采用版本控制来避免可能低概率出现
的并发问题。
在Hibernate中,使用Version注解来定义版本号字段。
将DirtyLock中的Account对象替换成AccountVersion,其他代码不变,执行出现异常。
package com.cdai.orm.hibernate.transaction;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name = "tb_account_version")
public class AccountVersion {
@Id
@Column(name = "col_id")
private long id;
@Column(name = "col_balance")
private long balance;
@Version
@Column(name = "col_version")
private int version;
public AccountVersion() {
}
public AccountVersion(long id, long balance) {
this.id = id;
this.balance = balance;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getBalance() {
return balance;
}
public void setBalance(long balance) {
this.balance = balance;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
log如下:
T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=?
Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=?
T1 - balance=1000
T2 - balance=1000
T1 - Change balance:900
T2 - Change balance:1100
Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=?
Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=?
T1 - Commit transaction
2264 [Thread-2] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.cdai.orm.hibernate.transaction.AccountVersion#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1934)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2578)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2478)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2805)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:114)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:180)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:375)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at com.cdai.orm.hibernate.transaction.VersionLock$2.run(VersionLock.java:93)
Both T1 and T2 are dead.
由于乐观锁完全将事务隔离交给数据库来控制,所以事务1和2交叉运行了,事务1提交
成功并将col_version改为1,然而事务2提交时已经找不到col_version为0的数据了,所以
抛出了异常。
分享到:
相关推荐
主要介绍了Java的Hibernate框架数据库操作中锁的使用和查询类型,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下
NULL 博文链接:https://12345678.iteye.com/blog/721836
NULL 博文链接:https://cdxs2.iteye.com/blog/1938245
乐观锁 求助编辑百科名片相对悲观锁而言,乐观锁...悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
21.4.1 利用数据库系统的独占锁来实现悲观锁 21.4.2 由应用程序实现悲观锁 21.5 利用Hibernate的版本控制来实现乐观锁 21.5.1 使用元素 21.5.2 使用元素 21.5.3 对游离对象进行版本检查 21.5.4 强制...
Hibernate 对数据库的并发支持 30 悲观锁(hibernate_pessimistic) 30 乐观锁(hibernate_optimistic) 32 HQL查询(hibernate_hql) 34 补充:SQL join连接 40 缓存 44 一级缓存 44 快取 45 hibernate二级缓存 47 ...
Hibernate锁的基本机制!主要是针对Hiberante乐观锁悲观锁的介绍以及相对应的数据库本身的处理机制,包括和SPRING相结合部分的锁处理机制
在并发访问情况下,很有可能出现不可重复读等等读现象。为了更好的应对高并发,封锁...乐观锁和悲观锁不仅在关系数据库里应用,在Hibernate、Memcache等等也有相关概念。 悲观锁:也即悲观并发控制,Pessimistic Concur
Hibernate 可以对类的属性或者方法进行注解。属性对应field类别,方法的 getXxx()对应property类别。通过 @Table 为实体Bean指定对应数据库表,目录和schema的名字。@Version 注解用于支持乐观锁版本控制。
21.4.1 利用数据库系统的独占锁来实现悲观锁 21.4.2 由应用程序实现悲观锁 21.5 利用Hibernate的版本控制来实现乐观锁 21.5.1 使用元素 21.5.2 使用元素 21.5.3 对游离对象进行版本检查 21.5.4 强制...
21.4.1 利用数据库系统的独占锁来实现悲观锁 21.4.2 由应用程序实现悲观锁 21.5 利用Hibernate的版本控制来实现乐观锁 21.5.1 使用元素 21.5.2 使用元素 21.5.3 对游离对象进行版本检查 21.5.4 强制...
21.4.1 利用数据库系统的独占锁来实现悲观锁 21.4.2 由应用程序实现悲观锁 21.5 利用Hibernate的版本控制来实现乐观锁 21.5.1 使用元素 21.5.2 使用元素 21.5.3 对游离对象进行版本检查 21.5.4 强制...
AUTO 生成器,适用与可移值的应用,多个@Id可以共享同一个 identifier生成器,只要把generator属性设成相同的值就可以。通过@SequenceGenerator 和 @TableGenerator 可以配置不同的 identifier 生成器。 table=...
超级经典的Hibernate经典简明教材,讲述了Hibernate的多对多、多对一、一对一、Lazy、一级缓、二级缓存存以及乐观锁与悲观锁等等。。。
假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。 基于jdbc实现的数据库加锁如下:。。。。...
Java企业级开发:Spring、Hibernate、MyBatis等框架原理 数据库和缓存:SQL优化、索引、Redis、Memcached等 分布式系统:负载均衡、集群、分布式事务、分布式锁等 使用人群: Java初/中级工程师,准备面试、,巩固和提升...
JSP+Servlet+Struts+Hibernate+Spring+Ajax》重点讲解了Struts 2、Speing和HIbernate框架的基础知识和高级技术,如Sruts 2中的*、类型转换、国际化和标签等,HIbe rna{e的会话、0/R映射和事务管理等,Spring中的...