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

Consistency for simultaneous UPDATES in Oracle, when the WHERE clause depends on the old value

我一直在阅读有关 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 table 所示,将是我会找到的最高分,如果我要扫描游戏 table 并以艰难的方式找到高分。

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

Conflicting Writes in Read Committed Transactions

In a read committed transaction, a conflicting write occurs when the transaction attempts to change a row updated by an uncommitted concurrent transaction, sometimes called a blocking transaction. The read committed transaction waits for the blocking transaction to end and release its row lock.

The options are as follows:

  • If the blocking transaction rolls back, then the waiting transaction proceeds to change the previously locked row as if the other transaction never existed.

  • If the blocking transaction commits and releases its locks, then the waiting transaction proceeds with its intended update to the newly changed row.

在我看来,如果红队(第 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 模式下,)您可以获得以下序列:(c.f。Table 9-2 在上面引用的 Oracle link 中)

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 Database permits a serializable transaction to modify a row only if changes to the row made by other transactions were already committed when the serializable transaction began. The database generates an error when a serializable transaction tries to update or delete data changed by a different transaction that committed after the serializable transaction began.

建议可序列化在上述情况下也不会做正确的事情,唯一的区别是事务 B 会出错,我可以选择回滚或重试。这是可行的,但似乎不必要地困难。

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

...如果是这样,我很困惑。这似乎是一件简单、常见的事情。在代码中,我可以通过在每个团队的高分测试和更新周围放置一个互斥体来轻松完成此操作。

第三个也是最重要的问题:如何让 Oracle(或任何 SQL 数据库,就此而言)达到我想要的目的?

更新: 进一步阅读表明我可能需要做一些明确的 table 锁定,如 (https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9015.htm) - 但我不是明确我需要什么。哈普!?

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

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

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

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

Tom Kyte 很好地解释了 Oracle 如何以及为什么会在内部重新启动 UPDATE 语句以实现读取一致性,此处:https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:11504247549852

is this a correct understanding of READ_COMMITED?

不完全是。您的场景 不是 您链接到的文档中 table 9-2 中显示的内容。您的场景基本上是 table 9-4.

中的内容

不同之处在于 9-2 版本(显示丢失的更新)不检查正在更新的值 - 它不过滤现有薪水,这是它正在更新的列。 9-4 版本正在更新一个 phone 数字,但作为更新的一部分查看该列的现有值,并且被阻止的更新最终没有更新任何行,因为它重新读取新更改的值 -现在与过滤器不匹配。

本质上,阻塞的更新是重新运行当删除以前的锁时,它会重新读取新提交的数据,并使用它来决定现在是否需要更新该行。

作为那个文档also says:

Locks achieve the following important database requirements:

  • Consistency

The data a session is viewing or changing must not be changed by other sessions until the user is finished.

  • Integrity

The data and structures must reflect all changes made to them in the correct sequence.

Oracle Database provides data concurrency, consistency, and integrity among transactions through its locking mechanisms. Locking occurs automatically and requires no user action.

最后两句话意味着您无需担心,通过手动更新来自两个会话的 table 中的同一行来验证行为相当容易。

automatic locks下:

A DML lock, also called a data lock, guarantees the integrity of data accessed concurrently by multiple users. For example, a DML lock prevents two customers from buying the last copy of a book available from an online bookseller. DML locks prevent destructive interference of simultaneous conflicting DML or DDL operations.

在您的情况下,当它重新启动在您的事务 B 中被阻止的更新时,它没有找到团队 1 的行,其中 highscore 小于 125。语句针对 [= 执行的数据30=]includes 从会话 A 提交的更新,即使该提交发生在 B 首次识别并要求锁定行时,该行 - 在那时 - 确实匹配其过滤器。所以它没有什么可以更新的,会话A的更新也没有丢失。