同时修改事务中的NDB实体不能按预期工作

时间:2017-06-26 00:17:23

标签: python google-app-engine transactions app-engine-ndb

我的理解是ndb.transactional用于确保函数正在处理最新数据。我在本地Google App Engine开发服务器的交互式控制台中测试了以下代码:

from google.appengine.ext import ndb

class UserModel(ndb.Model):

    level = ndb.IntegerProperty(default=0)

@ndb.transactional(retries=0)
def inc_user_lvl(user_key, recurse=True):
    user = user_key.get()
    print(user.level)
    user.level += 1
    if recurse:
        inc_user_lvl(user_key, recurse=False)
    user.put()

user_key = UserModel().put()
inc_user_lvl(user_key)
user = user_key.get()
print(user.level)

docs say

  

尝试的重试次数有限制(默认为3);如果事务仍然没有成功,NDB会引发TransactionFailedError

在这种情况下,重试次数为0,因此我希望用户的级别增加到1并且要提升TransactionFailedError

相反,函数调用成功,第二次调用inc_user_lvl对级别为1的用户进行操作(在第一次调用放置实体之前)。两次通话完成后,用户的等级为2。为什么会这样?

1 个答案:

答案 0 :(得分:2)

在事务中编写的代码将使用上下文缓存(特定于线程)

来自the docs

  

如果您不知道发生了什么,交易行为和NDB的缓存行为可能会让您感到困惑。如果修改事务中的实体但尚未提交事务,则NDB的上下文缓存具有修改后的值,但基础数据存储仍具有未修改的值。

这意味着对inc_user_lvl的第二次调用将从上下文缓存中提取UserModel实体,而不是ping数据存储区。您可以通过在ndb模型上设置_use_cache = False来解决此问题。 E.g。

class UserModel(ndb.Model):
    _use_cache = False
    level = ndb.IntegerProperty(default=0)

所以现在函数调用后用户的级别为1,但没有引发异常...

在交易中写入不会影响后续读取

由于某种原因,ndb文档中没有提到这一点。你必须查看被取代的版本(db)docs

  

此一致快照视图还扩展到在事务内写入后的读取。与大多数数据库不同,查询和获取Cloud Datastore事务不会在该事务中看到先前写入的结果。具体来说,如果在事务中修改或删除了实体,则查询或获取将返回事务开始时实体的原始版本,如果该实体不存在则返回任何内容。

这意味着,因为对inc_user_lvl的第二次调用是在第一次调用的事务中,所以获取用户实体将返回用户在事务开始时的状态。

您可以使用propagation=ndb.TransactionOptions.INDEPENDENT中的kwarg ndb.transactional开始单独的交易。有关交易选项的完整列表,请参阅the docs

@ndb.transactional(retries=0, propagation=ndb.TransactionOptions.INDEPENDENT)
def inc_user_lvl(user_key, recurse=True):
    user = user_key.get()
    user.level += 1
    if recurse:
        inc_user_lvl(user_key, recurse=False)
    user.put()

现在提出了预期的TransactionFailedError