乐观与悲观锁定

时间:2008-09-24 19:29:05

标签: sql-server locking optimistic-locking pessimistic-locking

我理解乐观和悲观锁定之间的区别。现在有人可以向我解释我何时会使用其中任何一个?

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

但是,只是为了检查,乐观意味着“在阅读时不要锁定桌子”,悲观意味着“在阅读时锁定桌子。”

10 个答案:

答案 0 :(得分:673)

Optimistic Locking是一种策略,您可以在其中读取记录,记下版本号(执行此操作的其他方法涉及日期,时间戳或校验和/哈希)并在写入之前检查版本是否未更改记录回来了。当你写回记录时,你过滤版本的更新,以确保它是原子的。 (即,在检查版本并将记录写入磁盘之间时尚未更新)并在一次更新中更新版本。

如果记录是脏的(即与您的版本不同),您将中止该交易,用户可以重新启动它。

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

Pessimistic Locking是您锁定记录以供独家使用,直到您完成它为止。它具有比乐观锁定更好的完整性,但要求您小心应用程序设计以避免Deadlocks。要使用悲观锁定,您需要直接连接到数据库(通常是two tier client server应用程序中的情况)或外部可用的事务ID,可以独立于连接使用。

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

答案 1 :(得分:145)

当你不期望很多碰撞时,会使用乐观锁定。执行正常操作的成本较低,但如果发生冲突,则会因交易中止而支付更高的价格来解决此问题。

预计发生碰撞时会使用悲观锁定。违反同步的交易只是被阻止了。

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

答案 2 :(得分:62)

乐观地假设在你阅读时没有任何改变。

悲观假设有某种东西会阻止它。

如果数据完全读取并不重要,请使用乐观。你可能会得到奇怪的“脏”读 - 但它不太可能导致死锁等。

大多数Web应用程序都可以使用脏读 - 在极少数情况下数据与下次重新加载不完全相符。

对于精确数据操作(如许多金融交易中),使用悲观。准确读取数据至关重要,没有未显示的变化 - 额外的锁定开销是值得的。

哦,并且Microsoft SQL服务器默认为页面锁定 - 基本上是您正在阅读的行以及其中的一些。行锁定更准确但速度更慢。通常值得将事务设置为read-committed或no-lock以避免在读取时发生死锁。

答案 3 :(得分:39)

除了已经说过的内容之外,应该说乐观锁定倾向于以牺牲可预测性为代价来提高并发性。悲观锁定往往会降低并发性,但更具可预测性。

你付钱等等

答案 4 :(得分:17)

当悲观锁定是更好的选择时,我会想到另一个案例。

对于乐观锁定,数据修改中的每个参与者必须同意使用这种锁定。但如果有人修改数据而不关心版本列,这将破坏乐观锁定的整个想法。

答案 5 :(得分:9)

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

  

乐观需要三层体系结构,在这种体系结构中,您不一定要为会话保持与数据库的连接,而悲观锁定是指在您完成之前锁定记录供您独占使用。它具有比乐观锁定更好的完整性,您需要直接连接到数据库。

Another answer is

  

乐观(版本控制)更快,因为没有锁定但是(悲观)锁定在争用较高时表现更好,并且最好防止工作而不是丢弃它并重新开始。

  

当您遇到罕见的碰撞时,乐观锁定效果最佳

本页

As it is put

我创建了我的答案来解释“保持连接”与“低冲突”的关系。

要了解哪种策略最适合您,请不要考虑数据库所具有的每秒事务数,而是考虑单个事务的持续时间。通常,您打开trasnaction,performa操作并关闭事务。这是一个简短的经典事务ANSI已经考虑到并且很好地逃脱了锁定。但是,您如何实施预订系统,许多客户同时预订相同的房间/座位?

您浏览优惠,填写表格,提供大量可用选项和当前价格。这需要花费很多时间,而且选项可能会过时,所有价格之间的价格无效,您开始填写表格并按“我同意”按钮,因为您访问的数据没有锁定,而其他人,更敏捷,有意思更改所有价格,您需要重新启动新价格。

您可以在阅读时锁定所有选项。这是一种悲观的情景。你明白为什么它很糟糕。您的系统可以由一个只是开始预订并吸烟的小丑打倒。在他结束之前,没有人能保留任何东西。您的现金流量降至零。这就是为什么乐观的保留在现实中使用。那些磨蹭太久的人不得不以更高的价格重新开始预订。

在这种乐观的方法中,您必须记录您读取的所有数据(如mine Repeated Read中所示)并使用您的数据版本到达提交点(我想以您在此处显示的价格购买股票)报价,而非当前价格)。此时,将创建ANSI事务,它会锁定数据库,检查是否有任何更改并提交/中止您的操作。 IMO,这是MVCC的有效模拟,它也与Optimistic CC相关联,并假设您的事务在中止的情况下重新启动,即您将进行新的预订。此处的交易涉及人类用户决策。

我不太了解如何手动实现MVCC,但我认为长期运行的事务选项是重启是了解主题的关键。如果我在任何地方都错了,请纠正我。我的回答是由this Alex Kuznecov chapter推动的。

答案 6 :(得分:7)

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

  • 如果有很多更新,悲观锁定很有用 用户尝试更新数据的可能性相对较高 时间。例如,如果每个操作都可以更新大量的操作 一次记录(银行可能会为每个人增加利息收入 帐户在每个月末),并运行两个应用程序 这样的操作同时会产生冲突。

  • 在包含经常更新的小表的应用程序中,悲观锁定也更合适。在这些所谓的热点的情况下,冲突很可能是乐观锁定浪费了回滚冲突交易的努力。

  • 如果冲突的可能性很大,乐观锁定很有用 低 - 有很多记录,但用户相对较少,或者更新很少,而且大部分都是读取操作。

答案 7 :(得分:7)

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

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

现在,让我们考虑以下Lost Update anomaly

Lost Update

丢失的更新异常可能发生在Read Committed隔离级别。

在上图中,我们可以看到Alice相信她可以从account中提取40,但是没有意识到Bob刚刚更改了帐户余额,现在该帐户中只剩下20。

悲观锁定

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

Lost Update Pessimistic Locking

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

因为爱丽丝和鲍勃都已经用account的PK值读取了1,所以在一个用户释放读取锁之前,他们都不能更改它。这是因为写操作需要获取写/排他锁,而共享/读锁会阻止写/排他锁。

只有在Alice提交了交易并且在account行上释放了读取锁之后,Bob UPDATE才会恢复并应用更改。在Alice释放读取锁之前,Bob的UPDATE会阻塞。

  

有关数据访问框架如何使用底层数据库悲观锁定支持的更多详细信息,请查看this article

乐观锁定

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

Application-level transactions

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

  

有关数据访问框架如何实现乐观锁定的更多详细信息,请查看this article

应用程序级交易

关系数据库系统已经出现在70年代末80年代初,当时客户通常会通过终端连接到大型机。因此,我们仍然看到数据库系统定义了诸如SESSION设置之类的术语。

如今,在Internet上,我们不再在同一数据库事务的上下文中执行读写操作,并且ACID不再足够。

例如,考虑以下用例:

enter image description here

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

因此,乐观锁定可以帮助您防止丢失更新,即使在使用还考虑了用户思考时间的应用程序级事务时也是如此。

  

有关应用程序级或逻辑事务的更多详细信息,请查看this article

结论

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

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

争用越多,冲突越多,中止交易的机会就越大。对于数据库系统而言,回滚可能会耗资巨大,因为它需要还原所有可能涉及表行和索引记录的当前所有挂起的更改。

由于这个原因,当冲突频繁发生时,悲观锁定可能更适合矿石使用,因为它减少了回滚事务的机会。

答案 8 :(得分:2)

乐观锁定的一个用例是让您的应用程序使用数据库来允许您的某个线程/主机“声明”任务。这是一种定期为我派上用场的技巧。

我能想到的最好的例子是使用数据库实现的任务队列,多个线程同时声明任务。如果任务的状态为“Available”,“Claimed”,“Completed”,则db查询可以说“Set status ='Claimed'where status ='Available'。如果多个线程尝试以这种方式更改状态,除了第一个线程之外的所有线程都会因为脏数据而失败。

请注意,这是仅涉及乐观锁定的用例。因此,作为“当您不期望发生许多冲突时使用乐观锁定”的替代方法,它也可以用于您希望发生冲突但只需要一个事务成功的地方。

答案 9 :(得分:1)

上面已经谈到了乐观锁定和悲观锁定的许多优点。 需要考虑的重要一点如下:

当使用开放式锁定时,我们需要谨慎对待应用程序如何从这些故障中恢复的事实。

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

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