【问题标题】:How to handle concurrent data modification mess and merge in a web service (Optimistic/Pessimistic Locking)? [duplicate]如何在 Web 服务中处理并发数据修改混乱和合并(乐观/悲观锁定)? [复制]
【发布时间】:2014-09-11 07:08:23
【问题描述】:

我有一个 Web 服务和几个后台运行的异步服务(用于长时间运行的数据收集过程),它们使用相同的 DAO 库和共享数据源。使用 Spring JDBC 模板实现的 DAO 库。 RDBMS 是 PostgreSQL。
当 Web 服务和异步服务通过 DAO 同时修改一个表中的相同行,结果数据不一致时,这种情况并不少见。
例如,我在一个实体中有“状态”字段,它可以取值:1 - 已付,2 - 未付。

有时我会遇到这样的情况:

  • 事务 #1:Web 服务正在修改“状态”值 1 到 2 到 ID = 1 的行。
  • 事务 #2:同时异步​​服务正在抓取一些 来自某些端点的数据并使用 ID 修改行的另一个字段 = 1。但是“state”字段仍然保持 1。它不知道“state”值在事务 #1 中从 1 更改为 2。

结果我的“状态”等于 1,尽管它必须是 2。它也有争议。有时异步服务会更改“状态”字段,而 Web 服务不知道此更改并再次造成数据混乱。当然,它不仅发生在 state 字段中。

我有两个选择:

  1. 使用悲观的 SELECT ... FOR UPDATE。
    但它不适合我 因为 Web 服务经常从表中获取部分行。它 异步服务保持时不能等待很长时间 锁定,因为这里的性能至关重要。
  2. 使用乐观锁定。 “版本”字段等等。
    但我不能只回滚 在乐观锁定失败的情况下发生变化,因为这会发生变化 必须合并。有时我不能只是重复操作,因为另一个系统中的原子操作不是 Spring jdbc 事务的一部分。

可能存在一些模式来处理诸如合并数据之类的情况?

谢谢,
伊万

【问题讨论】:

  • 它不完全是重复的,因为这个问题是关于长时间运行的操作和同一数据库上的多个较短操作的问题。其他问题及其任何答案解决这一点。

标签: java postgresql asynchronous concurrency spring-jdbc


【解决方案1】:

这是我使用的经验法则 - 意味着它可以适用于特殊用例...

  • 在单个事务中(每个请求我有一个写事务):悲观锁定,因为所有此类事务都很短(它们确实必须如此),因此获得锁的延迟是可以接受的,并且比必须回滚更好并在出现异常时重试 (*)
  • 在两个不同的请求处理之间(显示表单的 GET 和提交表单的 POST/PUT):乐观锁定,因为我不想长时间保持真正的数据库锁定

它非常简单、连贯,只有当事情真的出错时我才会重新考虑。

在单个事务中乐观锁定的唯一用例是,如果您有非常多的并发请求,并且每个用户都更新自己的数据,因此争用的风险很小 - 但您只能获得 lock 阶段,因为等待锁的概率同样低。

(*) 如果遇到乐观锁异常,则必须回滚事务,可以允许简单报错,也可以重试多次。

编辑:

如果我理解正确,您的问题是您有一个长时间运行的批处理操作和短暂的 Web 服务请求。

恕我直言,有三种方法可以解决这个问题,但目前没有足够的信息来选择最适合您的用例的方法

  • 在您的批次中使用许多短事务。这样,如果同时更新的概率很低,您可以使用乐观锁(带重试),或者如果您没有太多的同时 Web 服务请求,则可以使用悲观锁。这是最简单的方法,但如果您的业务规则要求批处理使用单个事务,则可能无法使用。
  • 在批处理和 Web 服务请求中使用悲观锁,但 Web 服务部分的超时时间很短。这样,如果您在 Web 服务中获得锁,您可以确定没有批次正在修改相同的值并且可以安全地提交。批量锁定后,您可以确定在提交之前没有 Web 服务会修改该值。如果 Web 服务无法获得锁,则只需向调用者返回一个错误。唯一的要求是 Web 服务的客户端可以稍后重试其请求。
  • 不要立即执行 Web 服务提交的事务,而是将它们排入队列,并在批处理操作结束时将它们全部处理。这样您就可以确定 Web 服务会立即返回,并且不会发生并发更新。但是您必须提供一种机制,以允许 Web 服务的客户端稍后在他们的请求可能被拒绝时查询其请求的结果,并且无论如何它假设请求可以被推迟。这适用于银行账户存款,需要反馈以进行检索,并且不能用于预订飞机上的座位。

【讨论】:

  • 感谢您的回答。正如我所写的问题更复杂,然后在乐观锁和悲观锁之间进行选择。尝试在示例中进行解释。异步服务开始工作并从数据库批次中选择行。然后它调用一些 Web 服务来获取一些数据来丰富这些行。 Web 服务调用需要很长时间。在所有更新之后,服务会保存行。我在这里有很长的交易,第 3 方 Web 服务调用不是交易的一部分。如果我将重复此调用,那么我将在 3rd 方系统中获得数据重复。
  • @Ivan : Oups 我一开始没有注意到,但看起来你的问题如下:你有 在同一个数据库 修改可以来自网络服务短事​​务和具有长时间运行事务的批次。你能确认一下,而且是说批次运行多长时间?
  • 是的,我确认。我在这里有 2 个问题:1)我有 3 个异步服务是批处理或逐个更新同一个表中的行,每天使用 3rd 方服务并且经常并行更新。选择行和更新行之间的间隔太长。由于与第 3 方服务的交互非常缓慢,从 10 秒到 10 分钟不等。 2)支付交易在同一张表中多次改变行的状态。似乎第一个解决方案很适合我,我会尝试的。谢谢。
【解决方案2】:

使用乐观锁定..并且由于版本不匹配而导致更新失败时不要回滚..而是重试直到成功。 您可能有重试次数的上限..

【讨论】:

  • 除非您使用保存点,否则回滚事务是 Postgres 中的唯一选项。一旦交易出现错误,您就不能“继续”
  • 我不能只是回滚。例如。服务 A 选择一些行并处理它们一些长时间运行的过程,最后对这些行进行批量更新。同时,服务 B 正在为服务 A 正在使用的这些行之一付款。现在我们的情况是服务 B 将状态从未付费更改为付费并提交对数据库的更改,但服务 B 不知道更改,并且在提交自己的更改时只会将状态从已付费更改为未付费。两种服务都使用 3rd 方服务,我无法再次重复操作。我需要某种合并。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
相关资源
最近更新 更多