【问题标题】:Why should I need "Application-level" repeatable-reads?为什么我需要“应用程序级”可重复读取?
【发布时间】:2016-01-28 17:42:03
【问题描述】:

在 Vlad Mihalcea 的伟大博客的 Preventing lost updates in long conversations 中说

为了防止丢失更新,我们必须有 应用程序级可重复 与并发控制机制一起读取

为什么我需要“应用程序级”可重复阅读?并发控制机制还不够吗?

注意:我以问答的方式写了这篇文章,因为阅读这篇文章让我怀疑使用 Hibernate + Optimistic Locking 的无状态后端的可能性。我已经做出了自己的结论(解释回答我自己的问题),但我仍然会犯错误或遗漏。

【问题讨论】:

    标签: sql hibernate transactions isolation-level optimistic-locking


    【解决方案1】:

    恕我直言存在三种丢失更新:

    • 破坏一致性(必须 100% 避免)并发生在两个或多个物理事务以并发方式修改相同数据并存在竞争条件的情况下。
    • 破坏一致性(必须 100% 避免)并发生在两个逻辑事务修改相同数据的情况下。
    • 在用户思考期间发生的情况(应该避免这种情况,以获得更好的用户体验)。

    第一个必须在数据库级别处理,如果我们没有超过 我们工具带中的隔离级别绝对不能使用 READ_COMMITED。我们至少应该使用 REPETEABLE_READ 来保证数据的一致性。

    如果我们的 RDBMS 使用两阶段锁定(如 SQL Server),并发性将受到影响,因为在读取查询中获得的共享锁将在事务结束(提交/回滚)时释放。

    相反,如果我们的 RDBMS 使用 MVCC(如 PostgreSQL),则可重复读取事务中的每个查询都会看到事务中第一个非事务控制语句开始时的快照,并且只会阻塞写入操作,如果排他锁已被占用,这意味着另一个事务正在写入相同的数据,如果此事务成功,则等待的必须回滚(错误:由于并发更新而无法序列化访问)。

    Hibernate 的乐观锁定使用与 PostgreSQL 中的 REPETEABLE_READ 相同的方法,并且都正确地避免了数据库级别的丢失更新。 主要区别在于 REPETEABLE_READ 不能考虑用户的思考时间,因为它不能进一步超出 pyshical 事务的边界,并且典型的 Web 应用程序需要在多个请求期间发生的读取-修改-写入会话模式。

    1. Alice 要求展示某种产品
    2. 从数据库中获取产品并返回到浏览器
    3. Alice 要求修改产品
    4. 产品必须更新并保存到数据库中

    在此对话中,可能有 2 到 3 个人修改了 Alice 在 1 处请求的产品,而 Alice 看不到该修改将被 Alice 3 中的请求覆盖。

    这类似于“无状态对话反模式”。好吧,我不这么认为,因为这是类型 2 的丢失更新,必须通过域的验证规则来避免(图中未考虑)。

    假设产品的最大库存为 10。 Alice 的请求类似于

    POST /purchase
    productId=1&quantity=3
    

    然后处理该请求的服务将执行以下操作:

    product = repository.retrieve(purchase.productId)
    if (product.quantity + purchase.quantity <= 10)
      product.quantity = product.quantity + purchase.quantity
      repository.save(product)
      return Http.OK
    else
      return Http.4XX
    

    仍然真实的是,Alice 可能会根据旧数据进行购买。

    1. Alice 要求展示某种产品
    2. 从数据库中获取产品(数量=5)并返回到浏览器
    3. Bob 发布购买(purchase.quantity=2 所以 product.quantity 现在是 7)
    4. Alice 看到 product.quantity=5,她希望该数量=8,因此创建了一个 POST (purchase.quantity=3)
    5. product.quantity=7 + purchase.quantity=3

    问题在于,在这种情况下,我们没有考虑用户思考时间,这会导致类型 3 的更新丢失。 这里的解决方案是使用 N_VERSION 将乐观锁定推送到应用程序层,就像 Vlad 说的那样,但他没有考虑将 N_VERSION 发送到客户端,并且没有这个选项,我们可以拥有一个无状态的后端。 甚至休眠consider this option。 场景是:

    1. Alice 要求展示某种产品
    2. 从数据库中获取产品(数量=5,版本=1)并返回到浏览器
    3. Bob 发布购买(purchase.quantity=2 所以 product.quantity 现在是 7 并且 version=2)
    4. Alice 发布购买 (purchase.quantity=3)
    5. 应用程序版本检查将引发并发错误(“Hey Alice,该产品已被其他用户更新”)

    可以说 N_VERSION 在 Javascript 客户端中很容易被破坏(即使我有 asked myself),但我们将有验证规则来维护业务不变量。

    所以我不同意弗拉德。我认为我们只需要业务规则和并发控制机制:乐观或悲观锁定(尽管使用悲观锁定我们无法避免丢失类型 3 的更新)。

    在我看来,应用程序级可重复读取的好处是使用扩展会话来维护请求之间的对象,从而避免在每个请求中重新获取它们。

    【讨论】:

      【解决方案2】:

      logical transaction 中,您可以有 N 个物理事务和 N - 1 个用户思考时间。

      用户思考时间也是逻辑事务的一部分,所以基本上你只有 1. 和 2.

      我看不出如何在没有pessimistic locking 或乐观锁定的情况下在用户思考时间通过域模型验证来防止lost update

      使用悲观锁定,您可以防止丢失更新,但只能在最后一个物理事务中,但前提是您考虑在最后一个事务中加载的状态,而不是在开始时加载的状态。这破坏了应用程序级的可重复读取保证。

      现在,您为什么还需要应用程序级的可重复读取?

      答案与隔离级别可重复读取的情况相同。通过可重复读取,您知道从读取到写入有一个可序列化的流程。如果没有此保证,您可以允许丢失更新异常。数据库事务也保留相同的语义。加载项目后,您可以防止丢失更新 (2PL) 或检测它 (MVCCC)。应用程序级可重复读取也这样做,但在多请求逻辑事务的上下文中,因此您需要保留您读取的相同行值,并且需要强制执行锁定机制。由于悲观锁定不会扩展 对于多个请求,乐观锁定是唯一需要考虑的可行锁定机制。

      所以,这种方法基本上是 MVCC 外推到多个请求。

      【讨论】:

      • “我看不出如何在没有悲观锁定或乐观锁定的情况下在用户思考时间通过域模型验证来防止丢失更新。”也许我不够清楚:确实,在用户认为时间内防止丢失更新的唯一方法是使用乐观锁定(悲观将需要整个逻辑事务的事务处于活动状态,这显然是一种反模式);但我想表达的是,在我们保持数据符合我们的业务规则的同时,在用户思考时间内丢失更新并不重要。
      • 但是,我更喜欢将 READ_COMMITED 与乐观锁定一起使用,因为 1. 允许更多并发事务,2PL 中的 REPETEABLE_READ 和 2. 允许考虑用户思考时间,尽管从以下角度来看这不是绝对必要的一致性,这是为了用户体验
      • 是的,我同意你的观点,但我认为这超出了这个问题的主题。 “有状态的版本完整对话”的读写偏差是正确的。
      • 顺便说一句,我认为在 REPETEABLE_READ 的任何 MVCC 实现中都不会发生读取偏差。在 PostreSQL 中如何避免像您在帖子中列出的那样。 PostgreSQL Documentation 并不意味着当它说: 可重复读取模式提供了严格的保证,即每个事务都看到一个完全稳定的数据库视图。但是,此视图不一定总是与同一级别的并发事务的某些串行(一次)执行一致。
      • 例如,即使是此级别的只读事务也可能会看到更新的控制记录以显示批处理已完成,但看不到逻辑上属于批处理,因为它读取了控制记录的较早版本。如果不仔细使用显式锁来阻止冲突的事务,尝试通过在此隔离级别上运行的事务来执行业务规则是不可能正常工作的。
      猜你喜欢
      • 2020-02-11
      • 2021-02-21
      • 1970-01-01
      • 2023-03-29
      • 1970-01-01
      • 2013-05-13
      • 1970-01-01
      • 2011-10-13
      • 2013-12-17
      相关资源
      最近更新 更多