谷歌应用引擎:如何处理并发(赛车条件)

时间:2013-01-27 10:26:49

标签: google-app-engine

我正在尝试解决基于this的赛车问题,以防止重复的用户注册。因此,如果帐户存在或电子邮件已被使用,则不会创建任何实体。

@ndb.transactional
def get_or_insert2(account, email):
    accountExists, emailExists = False, False
    entity = Member.get_by_id(account)
    if entity is not None:
        accountExists = True
    if Member.query(Member.email==email).fetch(1):
        emailExists = True
    if not accountExists and not emailExists:
        entity = Member(id=account)
        entity.put()
    return (entity, accountExists, emailExists)

我的问题:

  1. 我收到一条错误消息:BadRequestError:在事务中只允许祖先查询。问题是什么?

  2. 代码是否正确?我的意思是,它能真正解决赛车问题吗?

  3. 感谢。

1 个答案:

答案 0 :(得分:3)

Transactions处理实体组,您可以在跨群组事务中包含最多5个实体组。实体组由单个服务器(或组,已复制)处理,这意味着在检查数据或在实体组中执行祖先查询时,它能够具有一致的内部状态。

常规查询是全局的,具有最终一致性的索引。您不知道所有节点的所有更改何时都包含在索引中。您无法锁定整个数据存储区以获得事务的一致快照状态。如果您习惯使用查询的一致索引,这与常规RDBMS的关键区别。

对于1),问题在于您在事务中进行常规查询,这不会像上面解释的那样工作。 2)的答案然后变为否,查询无法解决赛车问题,你需要明确获取。

您需要一个会员,电子邮件和SSN模型。这是一个快速未经测试的例子,希望能让你前进:

class Member(ndb.Model):
    email = ndb.KeyProperty()
    ssn = ndb.KeyProperty()
    # More user properties goes here...

class Email(ndb.Model):
    member = ndb.KeyProperty()

class SSN(ndb.Model):
    member = ndb.KeyProperty()

@ndb.tasklet
def get_or_insert2(account, email, ssn):
    created = False
    member_key = ndb.Key(Member, account)
    email_key = ndb.Key(Email, email)
    ssn_key = ndb.Key(SSN, ssn)
    member_obj, email_obj, ssn_obj = yield ndb.get_multi_async([member_key, email_key, ssn_key])

    if member_obj is None and email_obj is None and ssn_obj is None:
        member_obj = Member(key=member_key, email=email_key, ssn=ssn_key))
        email_obj = Email(key=email_key, member=member_key)
        ssn_obj = SSN(key=ssn_key, member=member_key)
        yield ndb.put_multi_async([member_obj, email_obj])
        created = True

    raise ndb.Return([created, member_obj, email_obj, ssn_obj])

outcome = ndb.transaction(lambda: get_or_insert2(account, email, ssn), xg=True)

我不确定它是否可以合并@ ndb.tasklet和@ ndb.transactional(xg = True)装饰器,如果是,那么只需要试一试。

如果您需要根据电子邮件或ssn查询用户,您可以将KeyProperties重命名为* _ref并制作类似

的内容
@ndb.ComputedProperty
def email(self):
    return self.email_ref.id()

虽然最终会出现比您预期更多的代码行,但它在概念上很简单直接,您可以轻松找出以后再回过头来发生的事情。