我了解乐观锁定和悲观锁定之间的区别。现在有人可以向我解释一下什么时候可以使用它吗?

根据我是否使用存储过程执行查询,此问题的答案是否会改变?

但是只是检查一下,乐观的意思是“阅读时不要锁定桌子”,悲观的意思是“阅读时锁定桌子”。

评论

blog.couchbase.com/…

这是一个很好的问题,特别是因为我在可序列化性方面读到:在任何一种技术类型下,都应该检测和考虑冲突,并且对于物化和非物化冲突都具有类似的开销。

在SO上,您可以在这里找到关于Optimistic Locking的根本概念的很好的解释。

我建议阅读Martin Fowler关于模式的出色著作:martinfowler.com/books/eaa.html

#1 楼

乐观锁定是一种策略,您可以在其中读取记录,记下版本号(执行此操作的其他方法包括日期,时间戳或校验和/哈希),并在写回记录之前检查版本是否未更改。当您将记录写回时,您将过滤该版本的更新以确保它是原子的。 (即,在您检查版本并将记录写入磁盘之间没有更新)并一次命中更新版本。

如果记录很脏(即与您的版本不同),则您中止事务,用户可以重新启动它。

此策略最适用于大容量系统和三层体系结构,在这些系统中,您不必为会话维护与数据库的连接。在这种情况下,客户端实际上无法维护数据库锁定,因为连接是从池中获得的,并且您可能不会在同一访问中使用同一连接。

悲观锁定是在锁定记录时供您专用,直到完成为止。它具有比乐观锁定更好的完整性,但是需要您谨慎设计应用程序,以避免死锁。要使用悲观锁定,您需要直接连接到数据库(在两层客户端服务器应用程序中通常是这种情况)或可以独立于连接使用的外部可用事务ID。

在后一种情况下,您可以使用TxID打开事务,然后使用该ID重新连接。 DBMS保持锁定状态,并允许您选择通过TxID备份的会话。这就是使用两阶段提交协议(例如XA或COM +事务)的分布式事务的工作方式。

评论


乐观锁定不一定使用版本号。其他策略包括使用(a)时间戳或(b)行本身的整个状态。后一种策略很丑陋,但是在您无法修改架构的情况下,无需专用的版本列。

–安德鲁·斯旺(Andrew Swan)
09年12月8日在22:33

@geek-分布式事务协议(例如XA)允许在一个或多个系统周围建立单独的事务标识符。这种类型的协议确实允许通过事务连接从会话中分离并显式提供事务时,通过池化连接使用锁。但是,这确实会产生一些开销,并且如果您的应用程序不谨慎跟踪锁和事务标识符,则易于泄漏。这是一个重量级得多的解决方案。

– ConcernedOfTunbridgeWells
2014年7月1日9:46



@supercat-不同意乐观锁定的准确度小于100%-只要它检查所有输入记录中在整个期间内应保持不变的事务,就和悲观锁定(选择更新样式)一样准确相同的记录。主要区别在于,乐观锁仅在发生冲突时才产生开销,而悲观锁则减少了冲突时的开销。因此,在大多数事务不冲突的情况下最好是乐观的-我希望大多数应用程序通常都是这样。

– RichVel
2014年8月5日在11:33



@Legends-使用乐观锁定对于Web应用程序无疑是一种适当的策略。

– ConcernedOfTunbridgeWells
2015年4月2日在7:19

您应该提到,选择还取决于读取与写入的比率:如果您的应用程序主要是由许多用户组成的只读应用程序,并且有时您要写入数据,则不应该进行乐观锁定。例如,StackOverflow有很多人在阅读页面,有时有人编辑页面:在悲观锁定中,谁会得到该锁定?第一个?在乐观锁定中,希望编辑该页面的人只要拥有该页面的最新版本就可以执行此操作。

–jehon
15年5月5日在8:28



#2 楼

当您不希望发生太多冲突时,将使用乐观锁定。进行正常操作的成本较低,但是如果发生冲突,您将付出更高的代价来解决它,因为事务中止了。

在预期发生冲突时使用了悲观锁定。会简单地阻止违反同步的事务。

要选择适当的锁定机制,您必须估算读写量并相应地进行计划。

评论


在正常情况下,该语句是完美的,但在特殊情况下,您可以管理CAS操作,如答案中提到的@skaffman那样,可能会导致不准确,我会说这确实取决于。

–听
19年5月5日,11:33

#3 楼

乐观假设您在读取数据时不会有任何改变。

悲观假设某事物将因此而将其锁定。

如果不是必须的话,则可以完美地读取数据保持乐观。您可能会得到奇怪的“脏”读-却极少会导致死锁等。

大多数Web应用程序都可以进行脏读-在极少数情况下,数据不完全相同提示下一次重新加载确实有效。

对于悲观的数据操作(例如在许多金融交易中),请使用悲观主义。准确读取数据且没有未显示的更改非常重要-额外的锁定开销是值得的。

哦,Microsoft SQL Server默认使用页面锁定-基本上是您正在读取的行,并且两侧都有。行锁定更准确,但速度慢得多。通常值得将事务设置为已提交读或未锁定,以避免在读取时出现死锁。

评论


JPA Optimistic Locking使您可以保证读取一致性。

–吉利
08-09-24在19:43

读取一致性是一个单独的问题-在PostgreSQL,Oracle和许多其他数据库中,无论尚未提交任何更新,您都可以获得一致的数据视图,并且即使排他行锁也不会受到影响。

– RichVel
2014年8月1日13:52



我必须同意@RichVel。一方面,我可以看到,如果事务隔离级别为READ UNCOMMITTED,悲观锁定如何防止脏读。但是说乐观锁定容易受到脏读取的影响,却没有提到大多数数据库(包括显然的MS SQL Server)具有默认的隔离级别“ READ COMMITTED”,这会产生误导,这可以防止脏读取并使乐观锁定的准确性与悲观。

–抗菌素
2014年10月7日20:17



埃里克·布劳(Eric Brower)说,与其他银行家不同,银行家更喜欢肮脏的业务。您的专家似乎绝对不在手推车中。

–小外星人
16 Dec 5'在15:18

埃里克·布鲁尔(Eric Brewer)是给出CAP定理的专家,该定理谈到了银行业的一致性。它与您为之荣誉相反。

–小外星人
16 Dec 5'在22:05

#4 楼

处理冲突时,有两种选择:


您可以尝试避免冲突,这就是悲观锁的作用。
或者,您可以允许发生冲突。 ,但是您需要在提交事务时对其进行检测,这就是乐观锁定的作用。

现在,让我们考虑以下丢失更新异常:



“丢失的更新”异常可能发生在“读取已提交”隔离级别中。

在上图中,我们看到爱丽丝认为她可以从account中提取40,但是没有意识到鲍勃只是更改了帐户余额,现在该帐户只剩20个了。

悲观锁定

悲观锁定通过对帐户进行共享或读取锁定来实现此目标,从而防止Bob更改帐户。



在上图中,Alice和Bob都将获得两个用户都已读取的account表行的读取锁定。当使用“可重复读”或“可序列化”时,数据库将在SQL Server上获取这些锁。

由于Alice和Bob都已读取具有PK值account1,所以它们都不能更改它,直到一个用户释放了。读锁。这是因为写操作需要获取写/排他锁,而共享/读锁会阻止写/排他锁。

仅在Alice提交了事务并在account行上释放了读锁之后, ,Bob UPDATE将继续并应用更改。在Alice释放读取锁之前,Bob的UPDATE块会阻塞。


有关数据访问框架如何使用底层数据库悲观锁支持的详细信息,请查阅本文。


乐观锁定

乐观锁定允许发生冲突,但由于版本更改而在应用Alice的UPDATE时检测到冲突。



这次,我们还有一个version列。每次执行UPDATE或DELETE时,version列都会增加,它也用在UPDATE和DELETE语句的WHERE子句中。为此,我们需要在执行UPDATE或DELETE之前发出SELECT并读取当前的version,否则,我们将不知道将哪个版本值传递给WHERE子句或进行递增。


有关数据访问框架如何实现乐观锁定的更多详细信息,请查阅本文。


应用程序级事务

关系数据库系统在70年代末80年代初出现时,客户端通常会通过终端连接到大型机。这就是为什么我们仍然看到数据库系统定义诸如SESSION设置之类的原因。
如今,在Internet上,我们不再在同一数据库事务的上下文中执行读写操作,并且ACID不再足够例如,请考虑以下用例:



如果没有乐观锁定,就不可能捕获此丢失的更新即使数据库事务使用了Serializable。这是因为读写操作是在单独的HTTP请求中执行的,因此是在不同的数据库事务上执行的。

因此,即使使用包含用户思想的应用程序级事务,乐观锁定也可以帮助您防止丢失更新


有关应用程序级或逻辑事务的更多详细信息,请查阅本文。


结论

乐观锁定是一种非常有用的技术,即使在使用不太严格的隔离级别(如“读取已提交”)或在后续数据库事务中执行读写操作时,它也可以正常工作。

乐观锁定的缺点是,回滚将在捕获到OptimisticLockException时由数据访问框架触发,因此会丢失当前正在执行的事务之前我们所做的所有工作。

争论越多,冲突越多,中止交易的机会就越大。由于数据库系统需要还原所有可能涉及表行和索引记录的当前所有未决更改,因此回滚可能会付出高昂的代价。因此,当冲突频繁发生时,悲观锁定可能是合适的选择,因为它减少了回滚交易的机会。

评论


您建议在什么情况下选择OptimisticLocking和PessimisticLocking?它是否取决于OptimisticLockException发生的频率?

– Stimpson猫
6月3日10:34

@StimpsonCat从我从他的结论中得出的结论是,是的,如果您经常遇到异常,那么最好进行悲观锁定。就像我的情况一样,发生异常的可能性很小,因此我将进行乐观锁定。

–戴夫·克鲁斯
10月15日上午11:58

#5 楼

除了已经说过的话:


应该说,optimistic锁定倾向于以提高可预测性为代价来提高并发性。

Pessimistic锁定倾向于降低并发,但更可预测。您付钱等等...


评论


我看不到悲观锁定如何提高可预测性(无论您如何定义)-如果您的意思是“一旦获得锁定,事务就可以完成”,那么您是正确的,但是在事务拥有所有必需的锁定之前,它可能会面临获取延迟的麻烦。剩余的锁,实际上可能由于DB的死锁检测+解决逻辑而中止。使用悲观锁定的应用程序执行时间可能非常难以预测-经典示例是某人锁定一条记录X然后去吃午饭,然后用户锁定一条记录X和Y,然后又锁定另一个Y和Z,依此类推,直到大多数用户被阻止为止。 ..

– RichVel
2015年4月23日下午13:30

#6 楼

我会想到悲观锁定将是一个更好的选择的情况。

要进行乐观锁定,每个数据修改参与者都必须同意使用这种锁定方式。但是,如果有人修改数据而不关心版本列,则会破坏乐观锁定的整个想法。

评论


可以说,尝试使用乐观和悲观锁定的人也可以踩到对方的脚。想象一下这样一个场景:一个乐观会话读取一条记录并进行一些计算,而一个悲观会话更新该记录,然后乐观会话返回并更新同一记录而不注意到所做的任何更改。选择...仅在每个会话都使用相同语法的情况下进行更新。

–幻想
16年8月4日在22:09



#7 楼

基本上有两个最受欢迎的答案。第一个基本上是说


Optimistic需要三层体系结构,在这些结构中您不必为会话维护与数据库的连接,而悲观锁定则是您锁定独占记录时使用直到完成为止。与需要直接连接到数据库的乐观锁定相比,它的完整性要好得多。


另一个答案是



乐观(版本化)是由于没有锁定,所以速度更快,但是(竞争性)锁定在争用较高的情况下表现更好,并且最好是阻止工作而不是放弃工作并重新开始。





当发生罕见碰撞时,乐观锁最有效


在本页面。

我创建了答案来解释“保持连接”与“低碰撞”之间的关系。

要了解哪种策略最适合您,请不要考虑数据库具有的每秒事务数,而应考虑单个事务的持续时间。通常,您打开事务,执行操作并关闭事务。这是ANSI考虑到的简短而经典的事务,可以轻松摆脱锁定。但是,您如何实现一个票务预订系统,让许多客户同时预订相同的房间/座位?

您浏览报价,在表格中填写许多可用选项和当前价格。这需要花费大量时间,选项可能会过时,您之间的所有价格都将无效,开始填写表格并按“我同意”按钮,因为您访问的数据没有锁定,而其他人(更敏捷)已经介入更改所有价格,您需要重新设置新价格。

您可以在阅读它们时锁定所有选项。这是悲观的情况。您明白为什么会糟透了。您的系统可以由一个简单地启动预订并开始吸烟的小丑来关闭。在他完成之前,没有人可以保留任何东西。您的现金流量下降到零。这就是为什么在现实中使用乐观的保留。那些磨da时间太久的人必须以更高的价格重新开始预订。

采用这种乐观的方法,您必须记录所有读取的数据(例如在我的重复读取中),并随数据版本一起到达提交点(我想以您的价格购买股票显示在此报价中,而不是当前价格)。此时,将创建ANSI事务,该事务将锁定数据库,检查是否进行了任何更改并提交/中止操作。 IMO,这是MVCC的有效仿真,MVCC也与Optimistic CC相关联,并且还假定您的事务在中止的情况下重新启动,也就是说,您将进行新的保留。这里的事务涉及人类用户的决定。

我还远远不了解如何手动实施MVCC,但我认为可以选择重新启动的长期运行事务是理解该主题的关键。如果我在任何地方都不对,请纠正我。我的回答是由Alex Kuznecov的这一章所激发的。

#8 楼

在大多数情况下,乐观锁定会更有效并提供更高的性能。在悲观锁定和乐观锁定之间进行选择时,请考虑以下事项:


如果存在大量更新,并且用户尝试在以下位置更新数据的可能性相对较高,则悲观锁定非常有用相同的时间例如,如果每个操作一次可以更新大量的
记录(银行可能会在每个月末将利息收入添加到每个
帐户中),并且有两个应用程序正在运行
这样的操作将同时发生冲突。
悲观锁定在包含经常更新的小表的应用程序中也更合适。对于这些所谓的热点,很可能发生冲突,以至于乐观锁定浪费了回退冲突事务的精力。
如果发生冲突的可能性非常低,乐观锁定很有用
–有很多记录但用户相对较少,或更新很少,主要是读取类型的操作。


#9 楼

乐观锁定的一个用例是让您的应用程序使用数据库来允许您的线程/主机之一“声明”任务。

我能想到的最好的例子是使用数据库实现的任务队列,其中多个线程同时声明任务。如果任务的状态为“可用”,“已声明”,“已完成”,则数据库查询可以说类似“设置状态=“已声明”,其中状态为“可用”。如果多个线程尝试以这种方式更改状态,

请注意,这是一个只涉及乐观锁定的用例。因此,除了可以说“当您不期望太多锁定时使用乐观锁定”之外,冲突”,它也可以用于您希望发生冲突但希望一个事务成功的地方。

#10 楼

上面已经说了很多关于乐观和悲观锁定的好东西。
要考虑的重要一点如下:

使用乐观锁定时,我们需要谨慎这一事实:应用程序可以从这些故障中恢复。

特别是在异步消息驱动的体系结构中,这可能导致消息处理混乱或更新丢失。

需要仔细考虑失败的情况。