【问题标题】:Django ORM returning stale data, possible race conditionDjango ORM 返回陈旧数据,可能的竞争条件
【发布时间】:2015-12-16 04:27:01
【问题描述】:

考虑一个具有单个 RESTful API 的 Django 应用程序,该 API 创建对象(使用 Django REST 框架)。作为此 API 的一部分,我会进行一些验证以确保创建调用是幂等的,这样如果您调用创建 API 两次,第一次将成功,第二次将失败并显示自定义错误代码。

我有一个测试这个 API 的场景,它会以以下方式间歇性失败:

  1. 第一次 API 调用,成功,返回 201 -> 对象应该已创建
  2. 响应后立即进行第二次 API 调用
  3. 验证逻辑调用 MyModel.objects.get(some_field=some_value) 来检查这是否是重复调用
  4. 没有找到这样的对象,尽管在步骤 1 中创建了,因此创建了一个重复的对象
  5. 检查管理员/查询模型时,可以看到两个对象。

更多数据:

  • 此模型上没有显式缓存,或此过程中涉及的任何其他缓存。
  • 我无法在本地重现此内容
  • 在我的部署设置中,这种可能的竞争条件的失败率约为 5%。
  • 本地和部署都使用 PostgreSQL。
  • 部署环境确实启用了通用缓存,但是在本地启用缓存时仍然没有复制。

什么可能导致这种竞争状况? Django ORM 是否有任何失败模式,我可能会得到陈旧的数据?有什么方法可以防御性地保护验证免受陈旧数据的影响?

【问题讨论】:

  • 对于拥有 72,000 个互联网点的人,我不敢问这个问题,但您会显示步骤 3-4 的代码吗?另外,您确定步骤一和二是正确的,因为第二个调用是在第一个返回响应后开始的?
  • 您正在尝试使用检查和插入模式执行 upsert 或 insert-if-not-exists 。这行不通。您需要尝试插入,如果失败则捕获错误。或者使用 PostgreSQL 9.5 的 upsert 支持。见stackoverflow.com/q/17267417/398670
  • @YPCrumble 这里的代码无关紧要,我问了一个非常具体的问题,为什么 QuerySet 可能会返回陈旧的数据,是的,我特别提到第一个请求已经返回了 201回应。
  • @CraigRinger 我知道什么是 upsert,但事实并非如此。我只是在检查一个对象是否存在,以便在它存在时返回一个自定义错误。
  • @YuvalAdam insert-if-not-exists 就像您面临与 upsert 相同的竞争条件和相同的可见性问题。这基本上是 upsert 的退化案例。

标签: django postgresql python-3.x django-models django-rest-framework


【解决方案1】:

看看transaction.atomic:

https://docs.djangoproject.com/en/1.8/topics/db/transactions/#django.db.transaction.atomic

这有时可以解决此类问题

【讨论】:

    【解决方案2】:

    我目前提出的解决方案是基于@CraigRinger 的反馈,这似乎是正确的。基本上,为了从 Postgres 获得一致的响应,我需要实际尝试 INSERT,而不仅仅是查询数据,因为存在竞争条件。

    可以在https://code.djangoproject.com/ticket/20429#comment:22中找到对此的部分参考

    底线,解决方案是在模型的相关字段上添加 DB 强制 unique=True 约束(在本例中为 some_field),尝试创建对象,捕获 IntegrityError,然后从那里开始我可以实现自定义错误处理并将正确的结果传播到 API 层。

    【讨论】:

      猜你喜欢
      • 2013-04-13
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      • 2019-06-12
      • 2011-07-17
      • 2017-07-17
      • 2019-09-09
      • 1970-01-01
      相关资源
      最近更新 更多