乐观与悲观锁定

我理解乐观和悲观锁定之间的区别。现在,有人能向我解释一下我一般什么时候会使用其中的任何一个吗?

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

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

507495 次浏览

乐观主义者认为,当你阅读它时,什么都不会改变。

悲观主义者假设某事会发生,因此锁定它。

如果不需要完全读取数据,则使用乐观主义。您可能会得到奇怪的“脏”读取-但导致死锁等的可能性要小得多。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

另一个答案是

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

乐观锁定在很少发生碰撞时效果最好

正如它所说在这个页面上。

我创建了我的答案来解释“保持连接”如何与“低碰撞”相关。

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

您浏览报价,填写表格,其中包含大量可用选项和当前价格。这需要大量时间,选项可能会过时,您开始填写表格并按“我同意”按钮之间的所有价格无效,因为您访问的数据没有锁定,其他人更灵活,已经参与更改所有价格,您需要重新开始新的价格。

相反,你可以在阅读时锁定所有选项。这是一个悲观的场景。你明白为什么它很糟糕了。你的系统可能会被一个小丑击垮,他只是开始预订并开始吸烟。没有人能在他完成之前预订任何东西。你的现金流下降到零。这就是为什么现实中使用乐观预订。那些闲逛太久的人不得不以更高的价格重新开始预订。

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

我远远不了解如何手动实现MVCC,但我认为具有重启选项的长时间运行的事务是理解该主题的关键。如果我在任何地方错了,请纠正我。我的回答是由亚历克斯·库兹涅科夫一章驱动的。

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

我能想到的最好的例子是使用数据库实现的任务队列,其中多个线程同时声明任务。如果任务的状态为“可用”、“已声明”、“已完成”,数据库查询可以说“设置状态='已声明',其中状态='可用'。如果多个线程尝试以这种方式更改状态,除了第一个线程之外,所有线程都将因为脏数据而失败。

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

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

  • 如果有很多更新,那么悲观锁定是有用的用户尝试同时更新数据的可能性相对较高时间。例如,如果每个操作都可以更新大量一次记录(银行可能会将利息收入添加到每个每个月月底的帐户),并且两个应用程序正在运行这样的操作同时,它们会有冲突。

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

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

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

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

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

失败的场景需要深思熟虑。

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

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

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

丢失的更新

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

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

悲观锁定

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

丢失的更新悲观锁定

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

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

只有在Alice提交了她的事务并且在account行上释放了读锁之后,BobUPDATE才会恢复并应用更改。直到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时会触发回滚,因此会丢失我们之前由当前正在执行的事务完成的所有工作。

争用越多,冲突越多,中止事务的可能性就越大。回滚对数据库系统来说代价高昂,因为它需要恢复所有当前挂起的更改,这些更改可能涉及表行和索引记录。

因此,当冲突频繁发生时,悲观锁定可能更适合,因为它减少了回滚事务的机会。

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

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

乐观锁定和悲观锁定是在数据库中锁定数据的两种模型。

乐观锁定:只有当更改提交到数据库时,记录才会被锁定。

悲观锁定:记录在编辑时被锁定。

注意:在这两种数据锁定模型中,在将更改提交到数据库后释放锁。

假设在一个电子商务应用程序中,用户想要下订单。这段代码将由多个线程执行。在pessimistic locking中,当我们从数据库获得数据时,我们锁定它,这样没有其他线程可以修改它。我们处理数据,更新数据,然后提交数据。之后,我们释放锁。这里的锁定持续时间很长,我们从开始到提交都锁定了数据库记录。

optimistic locking中,我们获取数据并在不锁定的情况下处理数据。因此多个线程可以并发执行到目前为止的代码。这将加快速度。当我们更新时,我们锁定数据。我们必须验证没有其他线程更新该记录。例如,如果我们库存中有100个项目,我们必须将其更新为99(因为你的代码可能是quantity=queantity-1),但如果另一个线程已经使用了1,它应该是98。我们这里有race condition。在这种情况下,我们重新启动线程,以便从一开始就执行相同的代码。但这是一个昂贵的操作,你已经结束了,然后重新启动。如果我们有一些竞争条件,那没什么大不了的,如果竞争条件很高,就会有很多线程要重新启动。我们可能会循环运行。在竞争条件很高的情况下,我们应该使用'悲观锁定'

乐观锁定表示读取一行时不使用独占锁,因此不会阻止更新丢失写偏斜。因此,使用乐观锁定

  • 如果没有出现更新丢失写偏斜
  • 或者,即使出现更新丢失写偏斜也没有问题。

悲观锁定表示读取一行时使用独占锁,因此更新丢失写偏斜被阻止。所以,使用悲观锁定

  • 如果出现更新丢失写偏斜
  • 或者如果出现更新丢失写偏斜会出现一些问题。

mysqlPostgreSQL中,您可以将排他锁SELECT FOR UPDATE一起使用。

您可以在mysql中检查我对丢失更新的回答并编写歪斜示例乐观锁定(没有#0)悲观锁定(与#1