单个数据库事务中的并发
完全事务脚本、乐观(optimistic)锁、悲观(pessimistic)锁
完全事务脚本是一种解决方案是使用完全和其他事务隔离的事务,用数据库的话来说,就是隔离级别为serializable(串行化)的事务(悲观锁的另一种方案)。数据库保证:执行多个 serializable事务的结果和一个个串行执行它们的结果一样。serializable事务避免了更新丢失、读取不一致等问题。一些数据库还提供了repeatable read(能够保持一致的重复读取)和Read committed的隔离级别,完全隔离事务有两个主要优点:一是使用简单;二是避免了很多并发问题,包括修改丢失和读取不一致的问题。完全隔离事务的主要缺点是开销太大,降低了性能和规模扩展性,不管有没有并发更新,都需要额外开销。而且,由于死锁和其他并发相关问题,完全隔离事务比低隔离级别的事务的失败频率更高。用serializable或者repeatable read隔离级别的事务,这种方案不需要数据库模式变化。因为数据库的serializable事务机制能够处理并发更新,所以不需要用语句来锁住记录,也不需要维护版本号。在spring中可以通过配置数据源的属性“defaultTransactionIsolation”为“SERIALIZABLE”来实现。
乐观锁的工作原理是让应用程序检查它即将更新的数据是否已被另一个事务修改(自该数据上次读取以来)。实现乐观锁的一种常见做法是在每个表里添加一个版本字段,每次应用程序更新数据表记录时就增加这个版本字段。每个UPDATE语句中的WHERE子句会根据上次读取的值来判断这个版本号是否改变。使用诸如JDO和Hibernate的持久层构架时,实现乐观锁更为容易,因为它们已将乐观锁作为配置选项提供。
应用程序或持久层框架有三种方法可以判断一条记录自从上次读取出来后是否被修改过。
第一种方法是用一个version(版本)字段来跟踪记录修改状况,每次修改,version都会递增。事务只需要把原来读出的 version和当前version进行比较,就可以判断一条记录是否被修改过。应用程序检查和修改version字段是比较简单的做法,通常也是最好的做法。
第二种方法是用时间戳字段,每次应用程序修改数据,时间戳也会更新。事务只需要把原来读出的时间戳和当前时间戳进行比较,就可以判断一条记录是否被修改过。这个表结构也很容易实现,尤其是这种情况下,数据表经常已经有一个时间戳字段来记录用户修改记录的时间。然而,时间戳的问题是,如果两个修改操作之间的时间差小于时钟最小单位,那么一个事务可能覆盖另一个事务的修改。所以,只有在无法增加version字段的遗留系统中,才应该使用时间戳,否则,尽量使用version(版本)。
第三种方法是把上次读出的字段值和现有字段值进行比较。这种方法最大的好处是,不需要引入version或者时间戳字段,所以可以用在遗留系统中。这个方法的一个缺点是使得SQL UPDATE更加复杂,因为WHERE子句里面包含所有的字段的条件(具体原因我们后面会详述)。还必须正确处理null字段,可能比较复杂。比如,有一次我发现,一个持久层框架不能正确比较空字符串,因为Oracle把空字符串认为是null,这和Java不一样。我们在数据表里面增加了一个版本字段,解决了这个问题。
第三种方法的另一个缺点是,浮点数字段不能精确比较,浮点数字段的修改可能发现不了。由于这些问题,应用程序只有在别无选择,无法应用版本和时间戳的情况下,才应该使用这种方法。
悲观锁的工作原理是当读取某些记录时,事务先锁住这些记录,这样可以防止其他事务访问这些数据记录。具体细节要视数据库而定,不过糟糕的是,并非所有数据库都支持悲观锁。如果数据库支持悲观锁,在直接执行SQL语句的应用程序中,实现悲观锁非常容易。在JDO或Hibernate应用程序中使用悲观锁更为容易。JDO以配置选项的方式提供悲观锁,而Hibernate则提供一个简单实用的API,来锁定对象。
获取锁的机制是数据库相关的,并非所有数据库都支持。在Oracle数据库中,应用程序通过SELECT FOR UPDATE语句锁住选出的记录,从而实现悲观锁。如果已经有事务锁住记录,执行SELECT FOR UPDATE语句的事务就会被阻塞。如果其他事务更新、删除或者试图用SELECT FOR UPDATE选取这些记录,阻塞情况就会发生。事务一直阻塞,直到事务提交或者回滚。如果事务不想等待,可以采用SELECT FOR UPDATE NO WAIT,如果不能立即锁住记录,就返回ORA-00054错误。你还可以用SELECT FOR UPDATE WAIT 来指定等待时间。要注意的是:一些数据库对SELECT FOR UPDATE的用法有限制。例如,Oracle里面,SELECT FOR UPDATE只能用在顶层SQL,而不能嵌在子查询里面。还有一些SQL特性不能和SELECT FOR UPDATE一起使用。这些特性包括DISTINCT,集合统计函数(max、min、sum、count),GROUP BY。SELECT FOR UPDATE也不能用在某些类型的view和嵌套的SELECT里面。
处理长事务中的并发
乐观离线锁(Optimistic Offline Lock)模式、悲观离线锁(Pessimistic Offline Lock)模式
乐观离线锁模式是扩展此前描述的乐观锁机制,在编辑过程的最后一个数据库事务里,检查数据自最初读取后并未改变。例如,你可以使用共享数据表里的版本号字段实现这一机制。在编辑过程开始时,应用程序先将版本号存储在会话状态里。然后,当用户保存其更改时,应用程序进行检查,保证会话状态里保存的版本号和数据库中的版本号一致.由于乐观离线锁模式只在用户要保存修改后的数据时才进行检测,在实现诸如“修改订单”用例时,要用户放弃好几分钟才完成的操作,用户肯定会非常恼火,此时更好的选择是使用悲观离线锁.
悲观离线锁模式是在编辑过程开始之初,就锁定共享数据,以防止其他用户编辑该共享数据。这种方式与此前描述的悲观锁机制类似,只不过这里锁由应用程序而不是数据库实现。由于每次只有一个用户能编辑共享数据,因此可保证用户能保存自己的修改。
通知并发更新失败
当DAO或者数据库认为当前的两个事务不能并发运行时,就会发生并发失败。DAO可以允许JDBC SQLException传播到DAO的调用者。不过,有两个原因说明这种做法不是一个好主意。抛出SQLException的第一个问题是JDBC相关。应用程序也可以使用JDO这样的持久层,但是抛出其他的非JDBC异常。理想情况下,更高层的应用程序组件不应该知道底层访问了数据库。 SQLException的另一个问题是,SQLException是一个checked exception(非Runtime Exception,需要显式声明或者捕获的异常),需要DAO调用者或者捕捉SQLException,或者在方法签名上声明继续抛出 SQLException,这种情况下,调用者代码会变得杂乱。使用unchecked exceptions(Runtime Exception,不需要显式声明或者处理的异常)来报告并发失败要好得多。
分享到:
相关推荐
根据锁定时间,锁可分为悲观锁和乐观锁。悲观锁假设数据操作会发生冲突,所以在读取数据时立即锁定,例如表级锁。乐观锁则在数据更新时检查冲突,如行级锁,这样可以提高并发性能。 根据保护的对象,Oracle的锁分为...
**Hibernate**作为一种流行的Java持久层框架,提供了多种机制来处理并发控制问题,其中最常用的就是**乐观锁**和**悲观锁**。本文将详细介绍这两种锁的原理、应用场景以及如何在Hibernate中实现。 #### 二、悲观锁...
悲观锁和乐观锁各有优缺点,选择哪种锁机制取决于具体的应用场景和需求。悲观锁适合数据修改频繁且对数据一致性要求较高的场景;而乐观锁适用于数据修改较少且并发量较大的场景。在实际应用中,可以根据具体情况灵活...
【Hibernate乐观锁与悲观锁详解】 在开发过程中,尤其是在并发环境下,确保数据的一致性和完整性至关重要。Hibernate,作为Java领域广泛使用的ORM框架,提供了一种处理并发数据访问冲突的手段,那就是锁机制。主要...
其中,“乐观锁”和“悲观锁”是最为常见且有效的两种机制。本文将深入探讨这两种锁的原理及应用场景,帮助开发者更好地理解和选择合适的锁策略。 #### 二、悲观锁(Pessimistic Locking) 悲观锁是一种假设数据会...
本文主要讨论的是Hibernate框架中两种锁机制的使用:乐观锁和悲观锁。 首先,让我们深入理解悲观锁(Pessimistic Locking)。悲观锁正如其名字所示,假设并发环境中数据会被频繁修改,所以在整个数据处理过程中,它...
3. **并发控制机制**:如乐观锁和悲观锁等技术。 4. **定期检查锁定情况**:定期执行上述SQL查询,及时发现并解决锁定问题。 #### 五、总结 在Oracle数据库中,了解和掌握锁定机制对于确保数据完整性和提高系统...
- **并发控制**:使用Oracle提供的并发控制机制,如乐观锁或悲观锁。 通过以上方法,可以有效地分析并处理Oracle中的锁争用问题,提高系统的性能和稳定性。需要注意的是,在实际操作中应当谨慎处理锁争用问题,...
Oracle数据库的锁机制是保证其多用户环境下数据一致性与并发控制的关键技术。在数据库系统中,数据被多个用户并发访问时,如果不加以控制,可能导致数据的不一致性和并发问题。为了解决这个问题,Oracle引入了锁的...
PL/SQL提供了诸如锁定、乐观锁和悲观锁等机制来处理这些问题。 10. **错误处理和调试**:有效的错误处理是保证程序健壮性的重要手段。学习如何使用RAISE异常,以及如何使用DBMS_DEBUG_JDWP等工具进行调试,可以帮助...
虽然乐观封锁减少了锁的使用,但可能导致更高的冲突概率,因此在大多数情况下,Oracle推荐使用悲观封锁以保证数据的安全性。 在实际应用中,阻塞通常发生在INSERT、UPDATE、DELETE和SELECT...FOR UPDATE这些DML语句...
Oracle中的用户级(UL)锁就是一种悲观锁,它允许用户自定义锁机制,提供更细粒度的控制。 PL/SQL UL LOCK的实现通常涉及以下几个步骤: 1. **创建锁表**:首先,你需要创建一个表来存储锁定信息。这个表通常包含...
悲观锁和乐观锁是两种不同的并发控制策略。 - **悲观锁**:假设数据会发生冲突,因此在事务开始时即锁定数据,直到事务结束。这种方式虽然能有效避免冲突,但可能导致资源占用时间较长。 - **乐观锁**:假设数据...
锁机制可以分为悲观锁和乐观锁两种。 悲观锁是一种保守的锁机制,为任何操作(即使是 select)锁定资源,前提是假定用户冲突和回滚的可能性极大。乐观锁是在数据变化时才加锁,并发性高,前提是假定用户冲突是很小...
在本文中,我们将详细介绍 Hibernate 实现悲观锁和乐观锁的代码实现,并讨论 Hibernate 的隔离机制和锁机制。 Hibernate 的隔离机制 Hibernate 的隔离机制是基于数据库的事务隔离级别的。 Hibernate 提供了四种...
悲观锁可以使用 for update nowait 语句来加锁,而乐观锁可以通过增加标识列或使用 Oracle 的 ORA_ROWSCN 机制来实现。 阻塞与死锁 如果两个会话都持有另一个会话想要的资源,就会出现死锁。例如,可以打开两个...
### Oracle锁机制详解 #### 锁的基本概念与作用 锁机制是Oracle数据库中用于管理并发访问共享资源的关键组件。在多用户环境中,确保数据的一致性和完整性至关重要,尤其是在多个会话试图同时修改相同数据的情况下...
7. 数据库并发控制:讲述多用户环境下如何处理并发操作,包括锁定机制、乐观锁和悲观锁、多版本并发控制(MVCC)等。 8. 数据库恢复:介绍如何处理系统故障或错误,包括事务日志、检查点、前滚与后滚操作等。 9. ...
- **乐观锁**:假设很少发生冲突,因此在提交事务时检查是否有冲突,如版本控制或时间戳。 - **悲观锁**:在访问数据时立即锁定,防止其他用户修改,适合高冲突环境。 4. **锁定级别**: - **多粒度锁定...