在Oracle中实现乐观锁定

6
我对Oracle锁定的主题感到困惑。据我的研究,您可以使用FOR UPDATE NOWAIT/WAIT来锁定行。
我想用这种方式实现我的锁定。一旦我发出FOR UPDATE,该行将被锁定,然后我可以检查变异。我有一个versionNumber列,每次更新表时都会增加1。我可以使用此versionNumber来验证行是否已经变异或未变异,例如: if (:new.versionNum != :old.versionNum) raise_application_error(20000, 'Mutated'); end if; 我的问题是,在哪里编写FOR UPDATE代码?我制作了一个小型GUI来处理更改名字并将其保存回数据库。这是在oracle表上的触发器中完成还是在我的JDBC客户端的一侧完成?
任何澄清都很好!
谢谢

SELECT FOR UPDATE,但我认为这不是一种乐观锁定... - Vadim
哦,真的吗?你能详细说明一下“select for update”吗?我以为这会锁定行? - wonderBoy322
@wonderBoy322 它确实锁定了行,但我认为这是悲观锁定。这是“悲观”的,因为当你相信其他人会尝试修改同一行时,你使用它。与“乐观”相反,假设没有人会修改同一行,因此您需要完成所有工作,然后在最后检查某种标志以确保没有人更改任何内容。 - Jon Heller
通常,您可以通过拥有某种版本字段并更新“WHERE VERSION =:oldVersion”的方式来完成此操作。 - chrylis -cautiouslyoptimistic-
感谢您的所有输入!我仍然困惑于我应该在哪里编写这个?我的JDBC程序处理用户输入的新地址信息更新我的表。我是否应该在我的数据库上编写一个触发器,以检查版本类似于@chrylis所说的 BEFORE UPDATE? - wonderBoy322
一般来说,我会使用像 Hibernate 这样的 JPA 系统来自动完成。 - chrylis -cautiouslyoptimistic-
1个回答

10
有两种基本的锁定方法。
首先是悲观锁。在这种方法中,您会锁定行 (SELECT ... FOR UPDATE),从而防止其他人更改该行。然后执行 UPDATE。当您提交更改时,锁将被释放。在这种情况下,不需要版本号/时间戳列(至少不需要支持锁定),代码相对简单。
悲观锁的缺点是,当用户在页面上可能编辑数据时,需要在整个过程中都保持锁定状态。如果您正在构建基于Web的应用程序,则这在技术上非常困难,因为HTTP是无状态协议。最初呈现页面的请求通常会从连接池获取连接,执行 SELECT,然后在页面完成后将连接返回给池。更新数据的后续请求通常会在具有不同数据库会话的不同连接上进行,因此无法在第一会话中锁定该行并在第二会话中进行更新。如果要悲观锁定该行,则需要在后端执行大量工作,以确保一个数据库连接绑定到特定的中间层会话,直到用户完成编辑数据。这通常对可扩展性产生非常负面的影响,并引入各种会话管理问题-例如,您如何知道我请求了一个页面,锁定了一行,然后关闭了浏览器,而不是注销或进行更改?您将在数据库中锁定记录多长时间?如果某个其他会话正在尝试锁定该行,则会发生什么情况?如果第一个人出去吃午餐,您将让该会话块多长时间等待锁定?通常,人们不在基于Web的应用程序中实现悲观锁定,因为管理会话和会话状态太不切实际。
第二个选项是乐观锁。在这种方法中,您向行添加版本号/时间戳。查询数据时选择此版本号/时间戳。然后,在稍后执行更新时,在 WHERE 子句中使用此代码并检查实际修改了多少行。如果您修改了一行,就知道自读取它以来行没有更改。如果修改了 0 行,则知道该行已更改,您可以处理错误。
例如,您会选择数据以及版本号。
SELECT address_line1, city, state, zip, version
  FROM addressTable
 WHERE address_id = `<<some key>>`

当您准备进行更新时,您需要在UPDATE中使用version,并在行更改时抛出错误。

UPDATE addressTable
   SET address_line1 = `<<new address line 1>>`,
       city = `<<new city>>`,
       state = `<<new state>>`,
       zip = `<<new zip>>`,
       version = version + 1
 WHERE address_id = `<<some key>>`
   AND version = `<<version you read initially>>`

IF( SQL%ROWCOUNT = 0 )
THEN
  -- Darn.  The row must have changed since you read it.  Do something to
  -- alert the user.  Most likely, the application will need to re-query the
  -- data to see what the address has been changed to and then ask the user
  -- whether they want to re-apply the changes.
  RAISE_APPLICATION_ERROR( -20001, 'Oops, the row has changed since you read it.' );
END IF;

你的应用程序随后需要对错误进行一些有用的处理。通常情况下,这意味着执行类似于重新查询数据、向用户展示更改,并询问他们是否仍然想要应用这些更改的操作。例如,如果我读取一个地址并开始编辑它,然后去吃午饭,我的同事登录,读取了相同的地址,对其进行一些编辑并保存,那么当我回来尝试保存我的更改时,通常应该向我显示一些内容,告诉我我的同事已经将地址更改为新的内容-- 我是想继续进行编辑还是放弃它们。


第二种方法是我正在尝试实现的,但遇到了一些困难。在第一个例子中,你只需在SQL提示符中输入。但在第二个例子中,这也是在SQL提示符控制台中完成的吗?还是在addressTable上编写的触发器中完成的? - wonderBoy322
1
最后一行不应该是 END IF 吗? - nme
@JustinCave 我正在寻找相同的解决方案,您能否逐步粘贴可工作的代码,以便我可以执行它,或者您是否有一个专用的 GitHub 存储库,以便我可以从您的存储库中学习。 - deepakl.2000
@JustinCave,你能把这段代码发布到 GitHub 仓库吗? - deepakl.2000
@wonderBoy322 我正在寻找相同的解决方案,你能否逐步粘贴可工作的代码,以便我可以执行它,或者你有一个专用的 Github 存储库,以便我可以从中学习。 - deepakl.2000
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接