同时修改交易中的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)

文档说

试图的检索次数有一个限制(默认3(;如果交易仍然没有成功,NDB将提高TransactionFailedError

在这种情况下,重试的数量为0,所以我期望用户的级别增加到1,并且将提高TransactionFailedError

取而代之的是,函数呼叫既成功又是对inc_user_lvl的第二个调用,该函数在使用1级的用户上操作(在第一个调用已将实体放置之前(。两次通话完成后,用户的级别为2。为什么是这种情况?

交易中编写的代码将使用上下文缓存(线程特定(

来自文档:

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

这意味着对inc_user_lvl的第二个调用将从上下文缓存中拉出UserModel实体,而不是ping ping dataStore。您可以通过在NDB模型上设置_use_cache = False来解决此问题。例如

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

所以现在用户的级别是函数调用后的1个,但没有例外...

在交易中写入不影响随后的读取

由于某种原因,NDB文档中没有提到这一点。您必须查看取代版本(DB(文档:

这种一致的快照视图也扩展到在交易中写入后的读取。与大多数数据库不同,查询和进入云数据存储交易中的查询并未看到该事务内部写入的结果。具体而言,如果实体在事务中进行了修改或删除,则查询或获取事件从交易开始时返回实体的原始版本,或者如果不存在实体,则一无所有。

这意味着,由于对inc_user_lvl的第二个呼叫是在第一个呼叫的事务中,因此获得用户实体将返回用户,就像交易开始时。

您可以在ndb.transactional中使用Kwarg propagation=ndb.TransactionOptions.INDEPENDENT来启动单独的事务。有关交易的选项列表,请参见文档。

@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

最新更新