乐观与悲观锁定

Posted

技术标签:

【中文标题】乐观与悲观锁定【英文标题】:Optimistic vs. Pessimistic locking 【发布时间】:2010-09-12 20:39:46 【问题描述】:

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

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

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

【问题讨论】:

blog.couchbase.com/… 这是一个很好的问题,特别是因为在serializability 我读到了At any technique type conflicts should be detected and considered, with similar overhead for both materialized and non-materialized conflicts 在这里你可以找到一个很好的解释,在这里,关于 root concept of Optimistic Locking 是什么。 我建议阅读 Martin Fowler 的关于模式的好书:martinfowler.com/books/eaa.html 我认为并发控制比锁定更准确。 【参考方案1】:

乐观假设在您阅读时不会发生任何变化。

悲观主义假设某事会因此锁定它。

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

大多数 Web 应用程序都可以处理脏读 - 在极少数情况下,数据与下一次重新加载时的数据不完全一致。

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

哦,Microsoft SQL 服务器默认为页面锁定 - 基本上是您正在阅读的行和两侧的一些。行锁定更准确但慢得多。通常值得将您的事务设置为已提交读或无锁,以避免在读取时出现死锁。

【讨论】:

JPA 乐观锁定允许您保证读取一致性。 读取一致性是一个单独的问题 - 对于 PostgreSQL、Oracle 和许多其他数据库,无论尚未提交的任何更新如何,您都可以获得一致的数据视图,并且即使受到排他行锁的影响. 我必须同意@RichVel。一方面,如果您的事务隔离级别是未提交的,我可以看到悲观锁定如何防止脏读。但是,如果说乐观锁定容易受到脏读的影响,而没有提到大多数数据库(包括显然是 MS SQL Server)的默认隔离级别为“READ COMMITTED”,这是一种误导,它可以防止脏读并使乐观锁定与悲观。 埃里克·布劳尔说,与其他人不同,银行家更喜欢肮脏的操作。你的大师似乎完全没有手推车。 Eric Brewer 是提出 CAP 定理 says about consistency in banking 的大师。它与你的荣誉相反。【参考方案2】:

当您预计不会发生很多冲突时,会使用乐观锁定。进行正常操作的成本较低,但如果发生冲突,您将支付更高的价格来解决它,因为事务被中止。

当预计会发生冲突时,使用悲观锁定。违反同步的事务被简单地阻止。

要选择合适的锁定机制,您必须估计读取和写入的数量并做出相应的计划。

【讨论】:

在正常情况下,该语句是完美的,但在特殊情况下,您可以管理 CAS 操作,允许答案中提到的@skaffman 不准确,我想说这真的取决于.【参考方案3】:

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

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

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

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

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

【讨论】:

乐观锁定不一定使用版本号。其他策略包括使用 (a) 时间戳或 (b) 行本身的整个状态。后一种策略很难看,但在您无法修改架构的情况下避免了对专用版本列的需要。 乐观锁定的概念并不一定要求有一个 100% 可靠的方式来知道某事是否已被更改;无法检测到的更改是不可接受的,但偶尔的虚假更改报告可能并不算太糟糕,尤其是在收到此类报告的代码重新读取数据并检查数据是否实际发生更改时。 @supercat - 不同意乐观锁定低于 100% 的准确度 - 只要它检查所有输入记录以查找在此期间应保持不变的事务,它与悲观锁定一样准确(选择更新样式)相同的记录。主要区别在于乐观锁定仅在发生冲突时才会产生开销,而悲观锁定减少了冲突的开销。在大多数交易不发生冲突的情况下,如此乐观是最好的——我希望大多数应用程序通常都是这种情况。 @Legends - 使用优化锁定肯定是 Web 应用程序的合适策略。 您应该提到,选择还取决于读取与写入的比率:如果您的应用程序主要是很多用户的只读应用程序,并且有时您会写入数据,则不要乐观锁定。例如,*** 有很多人阅读页面,有时还有人编辑页面:在悲观锁定中,谁会获得锁定?第一个?在乐观锁定中,希望编辑页面的人只要拥有页面的最新版本就可以进行。【参考方案4】:

当悲观锁定将是更好的选择时,我会想到另一种情况。

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

【讨论】:

试图使用乐观锁和悲观锁的人也可能会踩到对方的脚,可以这么说。想象一个场景,乐观会话读取记录并进行一些计算,而悲观会话更新记录,然后乐观会话返回并更新同一记录,而不会注意到所做的任何更改。 Select ... for update 仅在每个会话都使用相同的语法时才有效。【参考方案5】:

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

乐观需要一个三层架构,您不必为会话维护与数据库的连接,而悲观锁定是您锁定记录以供您独占使用,直到您完成它。它比乐观锁定具有更好的完整性,您需要直接连接到数据库。

Another answer is

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

乐观锁定在发生罕见冲突时效果最佳

As it is put 在此页面上。

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

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

您浏览报价,在表格中填写大量可用选项和当前价格。这需要很多时间,选项可能会过时,您开始填写表格并按下“我同意”按钮之间的所有价格都无效,因为您访问的数据没有锁定,并且其他更敏捷的人干扰了更改所有价格,您需要以新价格重新开始。

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

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

我远未理解如何手动实现 MVCC,但我认为带有重启选项的长时间运行事务是理解该主题的关键。如果我在任何地方错了,请纠正我。我的回答是由this Alex Kuznecov chapter 激发的。

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

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

如果有大量更新和 用户尝试同时更新数据的机会相对较高 时间。例如,如果每个操作可以更新大量 一次记录(银行可能会将利息收入添加到每个 每个月底的帐户),并且两个应用程序正在运行 这样的操作同时进行,就会产生冲突。

悲观锁定也更适合包含经常更新的小表的应用程序。在这些所谓的热点的情况下,冲突的可能性如此之大,以至于乐观锁定浪费了回滚冲突事务的努力。

如果发生冲突的可能性非常大,乐观锁定很有用 低 - 记录很多但用户相对较少,或者更新很少并且主要是读取类型的操作。

【讨论】:

【参考方案8】:

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

在使用乐观锁时,我们需要注意应用程序将如何从这些故障中恢复。

特别是在异步消息驱动架构中,这可能导致消息处理无序或更新丢失。

需要考虑失败的场景。

【讨论】:

【参考方案9】:

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

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

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

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

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

悲观锁定

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

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

因为 Alice 和 Bob 都读取了 account 的 PK 值为 1,所以在一个用户释放读取锁之前,他们都无法更改它。这是因为写操作需要获取写/独占锁,而共享/读锁防止写/独占锁。

只有在 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 发生的频率? @StimpsonCat 从我从他的结论中读到的,是的,如果你经常遇到异常,那么最好进行悲观锁定。就像我的情况一样,发生异常的机会非常小,所以我会选择乐观锁定。 一旦 Bob 提款,DB 记录就会改变。因此,理想情况下,它应该反映给 Alice。这意味着,当 Alice 查询数量时,它应该更新数量而不是来自持久性上下文。我在这里错过了什么吗?谢谢。 赞成。尽管这些材料并不新颖,但随着越来越多的一次性家庭作业问题涌入系统,解释清楚的答案在 SO 中变得越来越少。 @EralpB Google、***、YoutTube、GitHub、Twitter、LinkedIn,到处都能找到我 ?【参考方案10】:

在更实际的情况下,更新分布式系统时,数据库中的乐观锁定可能不足以提供分布式系统所有部分所需的一致性。

例如,在基于 AWS 构建的应用程序中,通常在数据库(例如 DynamoDB)和存储(例如 S3)中都有数据。如果更新涉及 DynamoDB 和 S3,DynamoDB 中的乐观锁定仍可能使 S3 中的数据不一致。在这种情况下,使用 DynamoDB 中的悲观锁可能更安全,直到 S3 更新完成。事实上,AWS为此提供了locking library。

【讨论】:

FWIW,AWS DynamoDB 也支持乐观锁定。 docs.aws.amazon.com/amazondynamodb/latest/developerguide/…

以上是关于乐观与悲观锁定的主要内容,如果未能解决你的问题,请参考以下文章

MYSQL悲观锁与乐观锁

浅谈Mysql共享锁排他锁悲观锁乐观锁及其使用场景

乐观锁与悲观锁

Mysql共享锁排他锁悲观锁乐观锁及其使用场景

MySQL共享锁排他锁悲观锁乐观锁及其使用场景

数据库乐观锁与悲观锁