当WHERE子句依赖于旧值时,Oracle中同时更新的一致性

问题描述 投票:3回答:2

我一直在阅读关于Oracle数据一致性保证和支持的事务隔离级别(例如:https://docs.oracle.com/database/121/CNCPT/consist.htm#CNCPT121),我觉得我得到了很多高级信息,但我不确定它是如何适用于我的具体问题。

我将描述我的用例的简化版本,我正在寻找令人信服的答案,最好是引用,以便我需要如何构建我的事务以获得所需的结果。 (请不要在我的问题中过于依赖语法或数据规范化甚至数据类型;这是一个吸管人 - 所以如果你知道我的意思,继续前进并专注于并发问题。):)

场景(简化):

许多用户(数万)正在同时玩在线游戏。球员都是红队或蓝队两队的成员。每次玩家完成游戏时,我们都希望记录用户,他们的团队联盟,时间戳和分数。我们还希望汇总每个团队所取得的最高分。我们的数据模型如下所示:

// each game is logged in a table that looks kind of like this:
GAMES {
 time NUMBER,
 userid NUMBER,
 team NUMBER,
 score NUMBER
}
// high scores are tracked here, assume initial 0-score was inserted at time 0
HIGH_SCORES {
 team NUMBER,
 highscore NUMBER
}

因此,对于我收到的每个得分报告,我执行一个看起来像这样的交易

BEGIN
  UPDATE HIGH_SCORES set highscore=:1 WHERE team=:2 and :1>highscore;
  INSERT into GAMES (time, userid, team, score) VALUES (:1,:2,:3,:4);
COMMIT

我希望保留的不变量是,在任何时间点,每个团队的高分,如HIGH_SCORES表所示,如果我扫描GAMES表并找到高分,那么我将找到最高分。 。

我对READ_COMMITED隔离级别的理解表明这不会得到我想要的东西:

读取提交事务中的冲突写入

在读取已提交事务中,当事务尝试更改由未提交的并发事务(有时称为阻塞事务)更新的行时,会发生冲突写入。读提交的事务等待阻塞事务结束并释放其行锁。

选项如下:

  • 如果阻塞事务回滚,则等待事务继续更改先前锁定的行,就好像另一个事务从未存在一样。
  • 如果阻塞事务提交并释放其锁,则等待事务将继续其对新更改的行的预期更新。

在我看来,如果红队(队1)的得分高达100,并且两名队员同时提交更好的分数,那么多线程服务器可能会同时开始两个数据库事务:

# Transaction A
UPDATE HIGHSCORES set highscore=150 where team=1 and 150>highscore;
INSERT into GAMES (time, userid, team, score) VALUES (9999,100,1,150);

# Transaction B
UPDATE HIGHSCORES set highscore=125 where team=1 and 125>highscore;
INSERT into GAMES (time, userid, team, score) VALUES (9999,101,1,125);

所以(在READ_COMMITED模式下),您可以得到以下序列:(参见上面引用的Oracle链接中的表9-2)

A updates highscore for red team row; oracle locks this row

B still sees the 100 score and so tries to update red team highscore; 
  oracle Blocks trasaction B because that row is now locked with a conflicting write

A inserts into the games table;

A commits;

B is unblocked, and completes the update, clobbering the 150 with a 125 and my invariant condition will be broken.

第一个问题 - 这是对READ_COMMITED的正确理解吗?

然而,我阅读SERIALIZABLE:

Oracle数据库允许可序列化事务仅在可序列化事务开始时已经提交其他事务所做的行更改时修改行。当可序列化事务尝试更新或删除由可序列化事务开始后提交的其他事务更改的数据时,数据库会生成错误。

建议serializable在上面的场景中也不会做正确的事情,唯一的区别是事务B会出错并且我可以选择回滚或再试一次。这是可行的,但似乎不必要的困难。

第二个问题 - 这是对SERIALIZABLE的正确理解吗?

......如果是这样,我很沮丧。这似乎是一件简单,常见的事情。在代码中,我可以通过在每个团队的高分测试和更新周围放置一个互斥量来实现这一点。

第三个也是最重要的问题:如何将Oracle(或任何SQL数据库)提供给我想要的东西?

更新:进一步阅读建议我可能需要做一些显式的表锁定,如(https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9015.htm) - 但我不清楚我究竟需要什么。 HALP!?

sql oracle relational-database concurrentmodification transaction-isolation
2个回答
3
投票

哇,长问题。简短的回答是READ_COMMITTED就是你所需要的。

您不会得到丢失更新,因为事务B执行的UPDATE将在事务A提交后重新启动。 UPDATE在重新启动时将是读取一致的,而不是提交的时间点。

也就是说,在您的示例中,事务B将更新HIGH_SCORES中的0行。

在Oracle Concepts指南的第9章中有一个很好的例子,它演示了Oracle如何保护应用程序免受丢失更新的影响。

有一个很好的解释,说明Oracle将如何以及为什么在内部重新启动UPDATE语句,以便由Tom Kyte进行读取一致性,这里:https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:11504247549852


3
投票

这是对READ_COMMITED的正确理解吗?

不完全的。您的方案不是您链接到的文档中表9-2中显示的方案。您的方案基本上是表9-4中的内容。

区别在于9-2版本(显示丢失的更新)不检查正在更新的值 - 它不会过滤现有的工资,即它正在更新的列。 9-4版本正在更新电话号码,但在更新过程中查看该列的现有值,并且阻止的更新最终不会更新任何行,因为它会重新读取新更改的值 - 现在不会更新t匹配过滤器。

基本上,当锁定上一个锁被删除时,重新运行被阻止的更新,因此它重新读取新提交的数据,并使用它来决定是否现在需要更新该行。

作为该文件also says

锁实现以下重要数据库要求:

  • 一致性

在用户完成之前,其他会话不得更改会话正在查看或更改的数据。

  • 廉正

数据和结构必须以正确的顺序反映对它们所做的所有更改。

Oracle数据库通过其锁定机制在事务之间提供数据并发性,一致性和完整性。锁定自动发生,无需用户操作。

最后两句话意味着您不必担心它,并且通过从两个会话手动更新表中的同一行来验证行为相当容易。

automatic locks下:

DML锁定(也称为数据锁定)可确保多个用户同时访问的数据的完整性。例如,DML锁定可防止两个客户购买在线书商提供的书籍的最后一个副本。 DML锁可防止同时冲突的DML或DDL操作的破坏性干扰。

在您重新启动事务B中阻止的更新的情况下,它没有找到highscore小于125的团队1的行。语句执行的数据包括从会话A更新的提交,即使在B首次识别之后发生了提交,并要求在该行上锁定 - 在那一点 - 确实与其过滤器匹配。因此没有任何内容可以更新,并且会话A的更新不会丢失。

© www.soinside.com 2019 - 2024. All rights reserved.