同时修改事务内的 NDB 实体未按预期工作
Concurrently modifying NDB entities inside transactions not working as expected
我的理解是 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)
There is a limit (default 3) to the number of retries attempted; if the transaction still does not succeed, NDB raises TransactionFailedError
.
在这种情况下,重试次数为 0,所以我希望用户的级别增加到 1,并提高 TransactionFailedError
。
相反,函数调用都成功了,并且对 inc_user_lvl
的第二次调用对级别 1 的用户进行了操作(在第一次调用放置实体之前)。两次通话结束后用户等级为2。为什么会这样?
在事务中编写的代码将使用上下文缓存(特定于线程)
来自the docs:
Transaction behavior and NDB's caching behavior can combine to confuse you if you don't know what's going on. If you modify an entity inside a transaction but have not yet committed the transaction, then NDB's context cache has the modified value but the underlying datastore still has the unmodified value.
这意味着第二次调用 inc_user_lvl
将从上下文缓存中提取 UserModel
实体,而不是对数据存储执行 ping 操作。您可以通过在 ndb 模型上设置 _use_cache = False
来解决这个问题。例如
class UserModel(ndb.Model):
_use_cache = False
level = ndb.IntegerProperty(default=0)
所以现在函数调用后用户级别为 1 但没有引发异常...
事务中的写入不影响后续读取
出于某种原因,ndb 文档中没有提到这一点。您必须查看已取代的版本 (db) docs:
This consistent snapshot view also extends to reads after writes inside transactions. Unlike with most databases, queries and gets inside a Cloud Datastore transaction do not see the results of previous writes inside that transaction. Specifically, if an entity is modified or deleted within a transaction, a query or get returns the original version of the entity as of the beginning of the transaction, or nothing if the entity did not exist then.
这意味着,因为对 inc_user_lvl
的第二次调用是在第一次调用的事务中,所以获取用户实体将 return 用户在事务开始时的状态。
您可以使用 ndb.transactional
中的 kwarg propagation=ndb.TransactionOptions.INDEPENDENT
来启动单独的事务。有关交易选项的完整列表,请参阅 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
。
我的理解是 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)
There is a limit (default 3) to the number of retries attempted; if the transaction still does not succeed, NDB raises
TransactionFailedError
.
在这种情况下,重试次数为 0,所以我希望用户的级别增加到 1,并提高 TransactionFailedError
。
相反,函数调用都成功了,并且对 inc_user_lvl
的第二次调用对级别 1 的用户进行了操作(在第一次调用放置实体之前)。两次通话结束后用户等级为2。为什么会这样?
在事务中编写的代码将使用上下文缓存(特定于线程)
来自the docs:
Transaction behavior and NDB's caching behavior can combine to confuse you if you don't know what's going on. If you modify an entity inside a transaction but have not yet committed the transaction, then NDB's context cache has the modified value but the underlying datastore still has the unmodified value.
这意味着第二次调用 inc_user_lvl
将从上下文缓存中提取 UserModel
实体,而不是对数据存储执行 ping 操作。您可以通过在 ndb 模型上设置 _use_cache = False
来解决这个问题。例如
class UserModel(ndb.Model):
_use_cache = False
level = ndb.IntegerProperty(default=0)
所以现在函数调用后用户级别为 1 但没有引发异常...
事务中的写入不影响后续读取
出于某种原因,ndb 文档中没有提到这一点。您必须查看已取代的版本 (db) docs:
This consistent snapshot view also extends to reads after writes inside transactions. Unlike with most databases, queries and gets inside a Cloud Datastore transaction do not see the results of previous writes inside that transaction. Specifically, if an entity is modified or deleted within a transaction, a query or get returns the original version of the entity as of the beginning of the transaction, or nothing if the entity did not exist then.
这意味着,因为对 inc_user_lvl
的第二次调用是在第一次调用的事务中,所以获取用户实体将 return 用户在事务开始时的状态。
您可以使用 ndb.transactional
中的 kwarg propagation=ndb.TransactionOptions.INDEPENDENT
来启动单独的事务。有关交易选项的完整列表,请参阅 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
。