我理解乐观锁定和悲观锁定之间的区别。现在,谁能给我解释一下,我一般什么时候使用这两种方法?

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

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


乐观假设你读的时候什么都不会改变。

悲观的人认为某件事会发生,所以锁定它。

如果数据被完全读取不是必要的,请使用乐观。你可能会得到奇怪的“肮脏”解读——但它不太可能导致死锁或类似的情况。

大多数web应用程序都可以接受脏读——在极少数情况下,下一次重新加载时数据不完全一致。

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

对了,Microsoft SQL server默认为页面锁定——基本上就是你正在读的那一行和两边的几行。行锁定更准确,但速度要慢得多。通常值得将事务设置为读提交或无锁,以避免读取时发生死锁。


乐观锁定用于不期望发生太多冲突的情况。进行正常操作的成本较低,但如果碰撞确实发生,您将支付更高的代价来解决它,因为交易被中止。

悲观锁定在预期发生碰撞时使用。会违反同步的事务被简单地阻塞。

为了选择合适的锁定机制,您必须估计读取和写入的量并相应地进行计划。


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

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

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

悲观锁定是指将记录锁定为专属使用,直到使用完毕为止。它比乐观锁具有更好的完整性,但要求您在应用程序设计时要小心,以避免死锁。要使用悲观锁定,您需要一个到数据库的直接连接(在两层客户端服务器应用程序中通常是这样),或者一个可以独立于连接使用的外部可用事务ID。

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


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

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


基本上有两个最流行的答案。第一个基本上是说

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

另一个答案是

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

or

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

正如写在这一页上的。

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

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

你浏览这些优惠,在表格中填写大量可用的选项和当前价格。这需要花费大量时间,选项可能会过时,在你开始填写表格并按下“我同意”按钮之间,所有价格都无效,因为你访问的数据没有锁定,其他人更灵活,已经干涉更改了所有价格,你需要重新启动新的价格。

相反,您可以在阅读时锁定所有选项。这是一个悲观的情景。你知道为什么这么糟糕了吧。你的系统可以被一个小丑搞垮,他只是简单地开始预订,然后抽烟。没有人可以在他完成之前预订任何东西。你的现金流降为零。这就是为什么在现实中使用乐观保留。那些拖延太久的人不得不以更高的价格重新开始预订。

In this optimistic approach you have to record all the data that you read (as in mine Repeated Read) and come to the commit point with your version of data (I want to buy shares at the price you displayed in this quote, not current price). At this point, ANSI transaction is created, which locks the DB, checks if nothing is changed and commits/aborts your operation. IMO, this is effective emulation of MVCC, which is also associated with Optimistic CC and also assumes that your transaction restarts in case of abort, that is you will make a new reservation. A transaction here involves a human user decisions.

我远没有理解如何手动实现MVCC,但我认为具有重新启动选项的长时间运行的事务是理解这个主题的关键。如果我哪里说错了,请指正。我的回答是受到Alex Kuznecov这一章的启发。


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

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

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


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

Pessimistic locking is useful if there are a lot of updates and relatively high chances of users trying to update data at the same time. For example, if each operation can update a large number of records at a time (the bank might add interest earnings to every account at the end of each month), and two applications are running such operations at the same time, they will have conflicts. Pessimistic locking is also more appropriate in applications that contain small tables that are frequently updated. In the case of these so-called hotspots, conflicts are so probable that optimistic locking wastes effort in rolling back conflicting transactions. Optimistic locking is useful if the possibility for conflicts is very low – there are many records but relatively few users, or very few updates and mostly read-type operations.


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

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

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

需要仔细考虑失败场景。


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

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

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

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

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

悲观锁定

悲观锁定通过对帐户使用共享或读锁定来实现这一目标,从而阻止Bob更改帐户。

在上面的图中,Alice和Bob都将获得两个用户都读过的帐户表行上的读锁。当使用可重复读取或可串行化时,数据库在SQL Server上获得这些锁。

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

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

乐观锁定

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

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

应用级事务

关系数据库系统出现于70年代末80年代初,当时客户端通常通过终端连接到主机。这就是为什么我们仍然看到数据库系统定义诸如SESSION设置之类的术语。

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

例如,考虑以下用例:

如果没有乐观锁定,即使数据库事务使用Serializable,也无法捕获这个Lost Update。这是因为读写在不同的HTTP请求中执行,因此在不同的数据库事务上执行。

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

结论

乐观锁定是一种非常有用的技术,即使在使用不太严格的隔离级别(如Read Committed)或在后续数据库事务中执行读写时,它也能很好地工作。

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

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

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


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

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


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

乐观锁定:仅在向数据库提交更改时才锁定记录。

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

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


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

In optimistic locking, we get the data and process the data without locking. So multiple threads can execute the code so far concurrently. This will speed up. While we update, we lock the data. We have to verify that no other thread updated that record. For example, If we had 100 items in inventory and we have to update it to 99 (because your code might be quantity=queantity-1) but if another thread already used 1 it should be 98. We had race condition here. In this case, we restart the thread so we execute the same code from the beginning. But this is an expensive operation, you already came to end but then restart. if we had a few race conditions, that would not be a big deal, If the race condition was high, there would be a lot of threads to restart. We might run in a loop. In the race condition is high, we should be using `pessimistic locking


乐观锁定意味着在读取一行时不使用排他锁,因此不会防止丢失更新或写倾斜。所以,使用乐观锁定:

如果没有发生丢失的更新或写倾斜。 或者,即使发生更新丢失或写倾斜也没有问题。

悲观锁定意味着在读取一行时使用排他锁定,从而防止丢失更新或写倾斜。所以,使用悲观锁定:

如果发生更新丢失或写倾斜。 或者出现丢失更新或写倾斜等问题。

在MySQL和PostgreSQL中,SELECT FOR UPDATE可以使用排他锁。

你可以检查我丢失更新的答案,并在MySQL中使用乐观锁定(不使用SELECT FOR update)和悲观锁定(使用SELECT FOR update)编写倾斜示例。