【问题标题】:Multiple applications accessing the same data多个应用程序访问相同的数据
【发布时间】:2013-11-24 03:32:58
【问题描述】:

所以我的团队正在用 PHP 开发一个 API。 我主要关心的是 2 人调用更新同一行会发生什么。

因此,如果一个 API 调用先读取然后处理信息然后写入,那么如果另一个 API 调用对同一行数据执行相同的操作会发生什么情况。我们正在使用 Postgres 或 Mysql。

User has 100 dollars.
API Call 1 to ADD 20 1 reads 100.
API Call 2 to Subtract 20 reads 100.
API Call 1 writes 120;
API Call 2 write 80;( instead of 100);

我知道有一个涉及锁定表或行的解决方案,这能解决我的问题吗?

我需要一个 API 调用 2 不会失败,而是等待或重试的解决方案。

[编辑]

我应该更详细地说明我们正在做什么。这是一个浏览器 mmo 游戏,它有 2 个部分。一个 PHP REST API 和 Java 服务器。

浏览器进行 AJAX GET API 调用 ex。 :以 4 万美元建造工厂 PHP API 检查是否有足够的资金,并返回 JSON。 SO - 来自玩家的 4 万美元。

我们的服务器报价有并发数据库调用,使用 boneCP 来更新资金等值。

因此,在我之前的示例中,CALL 1 将是我们的 PHP API,而 CALL 2 将是服务器滴答声。

【问题讨论】:

  • 重新考虑 ACID 属性并确保它们被保留。之后检查 SS2PL
  • 使用可序列化隔离级别的 Postgres 会阻止您写入错误的值(尽管您必须处理错误)

标签: php mysql database postgresql


【解决方案1】:

你需要了解transactionstransaction isolationexplicit row-level locking

select ... for update

在您的情况下,还要考虑使用表达式。如果您发布以下内容,您的生活会简单得多:

update account set amount = amount - 20 where id = ?

【讨论】:

  • 我在 MySQL InnoDB 引擎中使用过事务,但是事务如何阻止它。 API 调用是分开的,它们不是通过同一个调用完成的。 (另外你的金额 = 金额 - 输入很简单,我不敢相信我忽略了这一点。(我刚刚看到你更新了)
  • 如果您有一个事务和一个选择更新,调用 2 无法读取该行,直到调用 1 完成。同样,如果两个并发事务是可序列化的。 (你有很多阅读要做。不要跳过这一步。)
  • 我知道第二次调用由于行锁而无法读取,但这是否意味着它失败或等待解锁?
  • @CrisG:通常,它会等到事务结束(成功与否)时释放锁。有时,它也会死锁和失败。
  • 您还应该考虑避免读-修改-写周期,以及乐观并发控制在哪些方面是不可避免的。
【解决方案2】:

您的 API 设计不当,需要修复。而不是:

  1. 用户有 100 美元。
  2. API 调用 1 到 ADD 20 1 读取 100。
  3. API 调用 2 减去 20 读取 100。
  4. API 调用 1 写入 120;
  5. API 调用 2 写入 80;(而不是 100);

你应该有这样的流程:

  1. 用户有100美元
  2. API 调用 1 到“将 20 添加到帐户 1”,因此帐户 1 从 100 更改为 120 并返回新值 120
  3. API 调用 2“从账户 1 中减去 20”,因此账户 1 从 120 更改为 100 并返回新值 100

同样,如果您在两个帐户之间进行转账,您将始终使用执行 转账 的 API 调用,而不是进行加法,然后再进行单独的减法,反之亦然反之亦然。

换句话说,您需要重新设计 API,以便您的数据库状态在每次 API 调用的开始和结束时保持一致。为了确保使用多个 SQL 语句的 API 调用即使在中途中止也保持一致,您必须 wrap the API call's work up in a transaction 并且如果您不打算使用 SERIALIZABLE 隔离,请执行适当的 SELECT ... FOR UPDATE 命令事务中的读-修改-写。

您需要避免客户端应用程序使用read-modify-write cycles

在读取-修改-写入不可避免的情况下,因此必须保持两个或多个 API 调用一致,您应该使用optimistic concurrency control 来防止更新冲突。在这种情况下,您的场景将如下所示:

  1. 用户有 100 美元。
  2. API 调用 1 读取帐户 1,获取值 100 和行版本 1
  3. API 调用 2 读取帐户 1,获取值 100 和行版本 1
  4. API 调用 1 将 20 添加到获取的值 100,发送新值 120 和 rowversion 1。成功,因为发送的 rowversion 等于数据库中的 rowversion。
  5. API 调用 2 从获取的值 100 中减去 20,发送新值 80 和新行版本 1。失败并返回错误,因为数据库行版本比发送的行版本新,所以其他人修改了行自从我们拿到它。第二个 API 调用者必须再次获取该行并重试。

【讨论】:

  • 好吧,我们拥有的是侦听 AJAX 调用并读取和写入数据库的 PHP API 脚本。我们还有一个 java 服务器,它会根据我们的算法不断地读取和更新数据库。这本质上是一个浏览器 mmo 游戏,其中 AJAX GET API 调用可能是“以 4 万美元建造一座建筑物”,然后为玩家更新数据库。但是,当所有这些都发生时,Java 服务器每 5 秒处理一次玩家 X 数量的恒定玩家收入的服务器滴答声。收入只是一个例子。 (如果有更好的方法,我想告诉你发生了什么)
  • @CrisG 好吧,一方面,如果收入不变,您可以存储自上次交易以来的时间 + 收入率,这样您就不会每 5 秒为每个玩家更新一次数据库,只是为了更新您的内容可以轻松计算。
  • 是的,我们确实通过使用时间戳来解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
  • 1970-01-01
  • 1970-01-01
  • 2013-06-27
  • 1970-01-01
相关资源
最近更新 更多