我想使用ndb创建用户,如下所示:
def create_user(self, google_id, ....):
user_keys = UserInformation.query(UserInformation.google_id == google_id ).fetch(keys_only=True)
if user_keys: # check whether user exist.
# already created
...(SNIP)...
else:
# create new user entity.
UserInformation(
# primary key is incompletekey
google_id = google_id,
facebook_id = None,
twitter_id = None,
name =
...(SNIP)...
).put()
如果在同一时间调用此函数两次,则会创建两个用户。("隔离"不确保在get()和put()之间)
所以,我在上面的函数中添加了@ ndb.transactional。 但是发生了以下错误。
BadRequestError:在事务中只允许祖先查询。
如何确保与非祖先查询隔离?
答案 0 :(得分:2)
ndb库不允许在事务内部进行非祖先查询。因此,如果您进行create_user()
事务处理,则会出现上述错误,因为您在其中调用了UserInformation.query()
(没有祖先)。
如果你真的想要这样做,你必须通过指定一个共同的祖先将所有UserInformation
个实体放在同一个实体组中,并使你的查询成为一个祖先。但这有性能影响,请参阅Ancestor relation in datastore。
否则,即使您将函数拆分为2,一个非事务性使查询后跟一个只创建用户的事务性 - 这将避免错误 - 您仍将面临数据存储最终一致性,这是实际上是您的问题的根本原因:查询的结果可能不会立即返回最近添加的实体,因为更新对应于查询的索引需要一些时间。这导致为同一用户创建重复实体的空间。见Balancing Strong and Eventual Consistency with Google Cloud Datastore。
一种可能的方法是稍后/周期性地检查是否存在重复并删除它们(最终将信息合并到单个实体中)。和/或将用户创建标记为“进行中”,记录新创建的实体的密钥并继续查询,直到密钥出现在查询结果中,最后将实体创建标记为“已完成”(您可能没有时间在同一个请求中这样做。)
另一种方法是(如果可能的话)确定算法以基于用户信息获得(唯一)密钥,并且仅检查具有这种密钥的实体是否存在而不是进行查询。密钥查找非常一致,可以在事务内部完成,这样就可以解决重复问题。例如,您可以使用google_id
作为密钥ID。举个例子,因为这也不理想:您可能没有google_id
的用户,用户可能想要更改他们的google_id
而不会丢失其他信息等。也许还会跟踪正在进行的用户创建会话信息,以防止重复尝试在同一会话中创建相同的用户(但这不会有助于不同会话的尝试)。
答案 1 :(得分:0)
对于您的用例,也许您可以使用ndb模型的>>> l = [1, 2, 3]
>>> '{%s}' % str(l).strip('[]')
>>> {1, 2, 3}
方法,根据API docs:
以交易方式检索现有实体或创建新实体。
所以你可以这样做:
get_or_insert
不会冒新用户的风险。
完整的文档:
classmethod get_or_insert(* args,** kwds)来源交易 检索现有实体或创建新实体。
Positional Args:name:要检索或创建的密钥名称。
关键字参数
namespace - 可选命名空间。 app - 可选的应用ID。
parent - 父实体密钥(如果有)。
context_options - ContextOptions对象(不是关键字args!)或无。
** kwds - 如果指定键名的实例尚未传递给模型类的构造函数的关键字参数 存在。如果具有提供的key_name和parent的实例已经存在 存在,这些参数将被丢弃。返回现有实例 具有指定键名和父键或新键的Model类 刚刚创建的。