我一直在阅读关于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表中所示,如果我要扫描牌桌,找到高分的话,我会找到最高的分数。
我对READ_COMMITED隔离级别的理解表明,这不会得到我想要的结果:
读提交事务中的冲突写入 在已提交的读事务中,当事务试图更改由未提交的并发事务(有时称为阻塞事务)更新的行时,会发生冲突写入。读取提交的事务等待阻塞事务结束并释放其行锁。 备选方案如下:
在我看来,如果红队(第一队)得分很高,并且两名球员同时提交更好的分数,那么多线程服务器可能会有两个数据库事务同时开始:
# 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。上面引用的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数据库只允许在可序列化事务开始时已提交其他事务对行的更改时,才允许可序列化事务修改行。当可序列化事务试图更新或删除在可序列化事务开始后提交的其他事务更改的数据时,数据库将生成错误。
建议串行化也不能在上面的场景中做正确的事情,唯一的区别是事务B会得到一个错误,我可以选择回滚或者重试。这是可行的,但似乎没有必要困难。
第二个问题--这是对SERIALIZABLE?的正确理解吗?
..。如果是这样的话,我就迷糊了。这似乎是一件简单而普通的事情。在代码中,我可以通过在每个团队的高分测试和更新周围设置一个互斥变量来实现这一点。
第三个也是最重要的问题:如何才能使Oracle (或任何SQL数据库)达到我想要的目的?
更新:的进一步阅读表明,我可能需要进行一些显式的表锁定,比如(9015.htm) --但我不清楚我到底需要什么。停下来!?
发布于 2019-03-08 17:46:08
好长的问题啊。简单地说,READ_COMMITTED
是你所需要的。
您不会得到丢失的更新,因为事务B执行的UPDATE
将在事务A提交后重新启动。UPDATE
将被读取--在重新启动的时间点上是一致的,而不是提交的时间点。
也就是说,在您的示例中,事务B将更新HIGH_SCORES
中的0行。
在Oracle概念指南第9章中有一个很好的例子,展示了Oracle如何保护应用程序免遭丢失的更新。
这里有一个很好的解释,说明甲骨文如何以及为什么要在内部重新启动UPDATE
语句以保持读取一致性,这里是:ID:11504247549852。
发布于 2019-03-08 17:49:56
这是对READ_COMMITED的正确理解吗?
不完全是。您的场景不是您链接到的文档中表9-2所示的场景。您的场景实质上是表9-4中的内容。
不同之处在于,9-2版本(显示丢失的更新)不检查正在更新的值--它不对现有薪资进行筛选,这是它正在更新的列。9-4版本正在更新一个电话号码,但是将该列的现有值作为更新的一部分,而阻止的更新最终不会更新任何行,因为它重新读取了新更改的值,而该值现在与过滤器不匹配。
实际上,当删除锁前锁时,阻塞的更新将重新运行,因此它重新读取新提交的数据,并使用该数据来决定该行现在是否需要更新。
作为那个文件还说
锁实现了以下重要的数据库要求:
在用户完成之前,会话正在查看或更改的数据不能被其他会话更改。
数据和结构必须以正确的顺序反映对它们所做的所有更改。
Oracle数据库通过其锁定机制在事务之间提供数据并发、一致性和完整性。锁定自动发生,不需要用户操作。
最后两句话意味着您不需要担心它,通过从两个会话手动更新表中的同一行来验证这种行为是相当容易的。
以及在自动锁下
DML锁(也称为数据锁)保证了多个用户并发访问的数据的完整性。例如,DML锁阻止两个客户购买网上书商提供的最后一本书。DML锁可防止同时发生冲突的DML或DDL操作的破坏性干扰。
在您的情况下,当它重新启动在您的事务B中被阻塞的更新时,它不会为highscore
小于125的团队1找到一行。该语句对其执行的数据包括会话A中更新的提交数据,尽管该提交发生在B首次标识并请求对行进行锁定之后,而在这一点上,该行确实与其筛选器匹配。因此,它没有什么可更新的,会话A的更新也没有丢失。
https://stackoverflow.com/questions/55067993
复制相似问题