协慌网

登录 贡献 社区

乐观锁定与悲观锁定

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

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

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

答案

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

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

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

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

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

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

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

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

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

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

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

丢失的更新

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

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

悲观锁定

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

丢失更新悲观锁定

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

因为 Alice 和 Bob 都已读取 PK 值为1 account ,所以直到一个用户释放读取锁之前,他们都无法更改它。这是因为写操作需要获取写 / 排他锁,而共享 / 读锁会阻止写 / 排他锁。

只有在 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 ,数据访问框架将触发回滚,因此将丢失当前正在执行的事务之前完成的所有工作。

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

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