`

数据库事务、hibernate悲观锁和乐观锁

 
阅读更多

1.ACID属性

原子性(atomic

  事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。

一致性(consistent

  事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。

隔离性(insulation

  由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。

持久性(durability

事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。

2.数据并发的问题

一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类,包括3类数据读问题(脏读、幻象读和不可重复读)以及2类数据更新问题(第一类丢失更新和第二类丢失更新)。下面,我们分别通过实例讲解引发问题的场景。

脏读(dirty read

A事务读取B事务尚未提交的更改数据,并在这个数据的基础上操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。来看取款事务和转账事务并发时引发的脏读场景:

时间

转账事务A

取款事务B

T1

 

开始事务

T2

开始事务

 

T3

 

查询账户余额为1000

T4

 

取出500元把余额改为500

T5

查询账户余额为500元(脏读)

 

T6

 

撤销事务余额恢复为1000

T7

汇入100元把余额改为600

 

T8

提交事务

 

在这个场景中,B希望取款500元而后又撤销了动作,而A往相同的账户中转账100元,就因为A事务读取了B事务尚未提交的数据,因而造成账户白白丢失了500元。

不可重复读(unrepeatable read

A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致

时间

取款事务A

转账事务B

T1

 

开始事务

T2

开始事务

 

T3

 

查询账户余额为1000

T4

查询账户余额为1000

 

T5

 

取出100元把余额改为900

T6

 

提交事务

T7

查询账户余额为900元(和T4读取的不一致)

 

在同一事务中,T4时间点和T7时间点读取账户存款余额不一样。

幻象读(phantom read

A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,举一个例子,假设银行系统在同一个事务中,两次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致

时间

统计金额事务A

转账事务B

T1

 

开始事务

T2

开始事务

 

T3

统计总存款数为10000

 

T4

 

新增一个存款账户,存款为100

T5

 

提交事务

T6

再次统计总存款数为10100元(幻象读)

 

如果新增数据刚好满足事务的查询条件,这个新数据就进入了事务的视野,因而产生了两个统计不一致的情况。

幻象读和不可重复读是两个容易混淆的概念,前者是指读到了其它已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除),为了避免这两种情况,采取的对策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新增数据,则往往需要添加表级锁——将整个表锁定,防止新增数据。

第一类丢失更新

A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来: 

时间

取款事务A

转账事务B

T1

开始事务

 

T2

 

开始事务

T3

查询账户余额为1000    

 

T4

 

查询账户余额为1000

T5

 

汇入100元把余额改为1100

T6

 

提交事务

T7

取出100元把余额改为900

 

T8

撤销事务

 

T9

余额恢复为1000元(丢失更新)

 

A事务在撤销时,不小心B事务已经转入账户的金额给抹去了。

第二类丢失更新

A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失

时间

转账事务A

取款事务B

T1

 

开始事务

T2

开始事务

 

T3

 

查询账户余额为1000

T4

查询账户余额为1000

 

T5

 

取出100元把余额改为900

T6

 

提交事务

T7

汇入100

 

T8

提交事务

 

T9

把余额改为1100元(丢失更新)

 

转账事务A覆盖了取款事务B对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务A先提交,那么用户账户将损失100元。

3.事务隔离级别

尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。此外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的。 

    SQL 92标准定义了4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。

1 事务隔离级别对并发问题的解决情况

隔离级别

脏读

不可

重复读

幻象读

第一类丢失更新

第二类丢失更新

READ UNCOMMITED

允许

允许

允许

不允许

允许

READ COMMITTED

不允许

允许

允许

不允许

允许

REPEATABLE READ

不允许

不允许

允许

不允许

不允许

SERIALIZABLE

不允许

不允许

不允许

不允许

不允许

事务的隔离级别和数据库并发性是对立的,两者此增彼长。一般来说,使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,而使用SERIALIZABLE隔离级别的数据库并发性最低。

    SQL 92定义READ UNCOMMITED主要是为了提供非阻塞读的能力,Oracle虽然也支持READ UNCOMMITED,但它不支持脏读,因为Oracle使用多版本机制彻底解决了在非阻塞读时读到脏数据的问题并保证读的一致性,所以,OracleREAD COMMITTED隔离级别就已经满足了SQL 92标准的REPEATABLE READ隔离级别。

SQL 92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据应用的需要选择适合的隔离等级。

4.hibernate悲观锁和乐观锁

悲观锁:

Account.java

package com.test.hiberenate.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Account {
	private int id;
	private int balance;

	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getBalance() {
		return balance;
	}

	public void setBalance(int balance) {
		this.balance = balance;
	}

}

 

HibernateTest.java

package com.test.hibernate.model;

import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.test.hiberenate.model.Account;

public class HibernateTest {
	private static SessionFactory sf = null;

	@BeforeClass
	public static void beforeClass() {
		try {
			if (sf == null) {
				sf = new Configuration().configure().buildSessionFactory();
			}
		} catch (HibernateException e) {
			e.printStackTrace();
		}
	}

	@AfterClass
	public static void afterClass() {
		sf.close();
	}

	@Test
	public void testSchemaExport() {
		new SchemaExport(new Configuration().configure()).create(false, true);
	}

	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();

		Account a = new Account();
		a.setBalance(100);
		session.save(a);

		session.getTransaction().commit();
		session.close();
	}

	@Test
	public void testPessimisticLock() {
		Session session = sf.openSession();
		session.beginTransaction();
		// 给记录添加锁
		Account a = (Account) session.load(Account.class, 1, LockOptions.UPGRADE);
		int balance = a.getBalance();
		a.setBalance(balance - 10);
		session.getTransaction().commit();
		session.close();
	}

	public static void main(String[] args) {
		beforeClass();
	}

}

 

乐观锁:

Account.java

package com.test.hiberenate.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Account {
	private int id;
	private int balance;
	private int version;

	@Version
	public int getVersion() {
		return version;
	}

	public void setVersion(int version) {
		this.version = version;
	}

	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getBalance() {
		return balance;
	}

	public void setBalance(int balance) {
		this.balance = balance;
	}

}

 

HibernateTest.java

package com.test.hibernate.model;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.test.hiberenate.model.Account;

public class HibernateTest {
	private static SessionFactory sf = null;

	@BeforeClass
	public static void beforeClass() {
		try {
			if (sf == null) {
				sf = new Configuration().configure().buildSessionFactory();
			}
		} catch (HibernateException e) {
			e.printStackTrace();
		}
	}

	@AfterClass
	public static void afterClass() {
		sf.close();
	}

	@Test
	public void testSchemaExport() {
		new SchemaExport(new Configuration().configure()).create(false, true);
	}

	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();

		Account a = new Account();
		a.setBalance(100);
		session.save(a);

		session.getTransaction().commit();
		session.close();
	}

	@Test
	public void testOptimisticLock() {
		Session session = sf.openSession();
		Session session2 = sf.openSession();

		session.beginTransaction();
		Account a1 = (Account) session.load(Account.class, 1);
		session2.beginTransaction();
		Account a2 = (Account) session2.load(Account.class, 1);

		a1.setBalance(900);
		a2.setBalance(1100);

		session.getTransaction().commit();
		session2.getTransaction().commit();

		session.close();
		session2.close();
	}

	public static void main(String[] args) {
		beforeClass();
	}

}

 

分享到:
评论

相关推荐

    hibernate乐观锁

    求助编辑百科名片相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,...

    MySQL数据库锁机制原理解析

    在并发访问情况下,很有可能出现不可重复读等等读现象。为了更好的应对高并发,封锁...乐观锁和悲观锁不仅在关系数据库里应用,在Hibernate、Memcache等等也有相关概念。 悲观锁:也即悲观并发控制,Pessimistic Concur

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    POJOs.in.Action

    《POJOS IN ACTION中文版:用轻量级框架开发企业应用》是一本实践指南,它围绕POJO...此外,《POJOS IN ACTION中文版:用轻量级框架开发企业应用》还详尽地分析了事务管理、悲观锁、乐观锁、条件组合搜索等难点问题。

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    Grails 中文参考手册

    5.5.2.7 乐观锁和版本定义 5.5.2.8 立即加载和延迟加载 5.6 事务编程 5.7 GORM和约束 6. Web层 6.1 控制器 6.1.1 理解控制器和操作 6.1.2 控制器和作用域 6.1.3 模型和视图 6.1.4 重定向和链 6.1.5 控制器拦截器 ...

    涵盖了90%以上的面试题

    乐观锁和悲观锁 偏向锁 轻量级锁 自旋锁 自适应自旋锁 重量级锁 synchronized 可重入锁 土方法实现可重入锁 使用AQS类实现可重入锁 CAS MySQL 中的行级锁、表级锁和页级锁 java中的死锁 公平锁和非公平锁 锁的总结 ...

    jdbc基础和参考

    对托管对象的更动,在托管期间不会影响数据库,但是将托管状态重新和数据库进行关联的时候会将托管对象重新变为持久态,那么在托管期间发生的更动也会被更新到数据库中 get()/load():从数据库中还原数据 get: 1.先...

    Hibernate开发指南

    悲观锁(Pessimistic Locking) ....................................... 70 乐观锁(Optimistic Locking)..........................................71 Hibernate 分页 ............................................

    低清版 大型门户网站是这样炼成的.pdf

    5.7.3 悲观锁 327 5.7.4 乐观锁 328 5.8 hibernate的缓存机制 332 5.8.1 hibernate的缓存分类 332 5.8.2 hibernate的缓存范围 332 5.8.3 hibernate的缓存管理 333 5.8.4 hibernate二级缓存的并发访问策略 333 ...

    Java常见面试题208道.docx

    176.说一下乐观锁和悲观锁? 177.mysql 问题排查都有哪些手段? 178.如何做 mysql 的性能优化? 十八、Redis 179.redis 是什么?都有哪些使用场景? 180.redis 有哪些功能? 181.redis 和 memecache 有什么区别? ...

    Java学习笔记-个人整理的

    {13.3}连接Oracle数据库及操作}{192}{section.13.3} {13.4}批处理模式}{195}{section.13.4} {13.5}分页查询}{196}{section.13.5} {13.5.1}MySQL}{198}{subsection.13.5.1} {13.6}连接池}{199}{section.13.6} {...

Global site tag (gtag.js) - Google Analytics