简体   繁体   English

Google App Engine:查询执行由ndb.transaction装饰的函数时如何交互

[英]Google App Engine: How to do queries interact when they execute a function decorated by ndb.transaction

Just to understand the inner workings of ndb.transaction, I tried the following experiment. 为了了解ndb.transaction的内部工作原理,我尝试了以下实验。

The Conference API in Udacity example was used to create a conference with just one seat available. Udacity中的Conference API示例用于创建只有一个席位的会议。

I added a wait in the method that handles the registerForConference API as shown below. 我在处理registerForConference API的方法中添加了一个等待,如下所示。 I put logging debug messages to understand the flow as shown below 我将日志记录调试消息理解为如下所示的流程

I started two calls of the registerForConference API one after the other. 我一个接一个地开始了两次调用registerForConference API。

Without ndb.transaction, both will return true (ie both return registed successfully). 如果没有ndb.transaction,则两者都将返回true(即,两者都将成功注册)。 With ndb.transaction, the second will return false. 使用ndb.transaction,第二个将返回false。 So, things are working as expected. 因此,一切按预期进行。

But the sequence of the steps the second request went through was unexpected. 但是第二个请求执行的步骤顺序是意外的。 I was expecting the second query to get stuck at some point until the first one completes and a TransactionFailedError will be thrown when it tries to insert. 我期望第二个查询会在某个时候卡住,直到第一个查询完成,并且在尝试插入时将引发TransactionFailedError Instead, it looks like the second request actually falls through the method once and then re-executes the method after the first one completes. 相反,看起来第二个请求实际上一次通过了该方法,然后在第一个请求完成后重新执行该方法。 In the second exception it reads the updated value of seatsAvailable and determines it can't register. 在第二个异常中,它读取seatAvailable的更新值,并确定它无法注册。 Is this the expected behavior? 这是预期的行为吗? If yes, isn't this wasteful since some of the steps can be done in parallel and only steps with conflict need to be executed after first query completes? 如果是的话,这是否不浪费资源,因为某些步骤可以并行执行,并且在第一次查询完成后仅需要执行有冲突的步骤?

Sequence of debug log messages printed 打印调试日志消息的顺序

/_ah/spi/ConferenceApi.regForAConf
D 18:28:21.093 Checking for id_token.
D 18:28:21.093 id_token verification failed: Token is not an id_token (Wrong number of segments)
D 18:28:21.093 Checking for oauth token.
D 18:28:21.101 Returning user from matched oauth_user.
D 18:28:21.111 Entered conf registration check
D 18:28:21.125 Got a profile object
D 18:28:21.131 Got a conf object
D 18:28:21.131 Entered updating step
**Went through the entire method once**
**Then restarted after first API completed**
D 18:28:46.143 Leaving with exit value 1
D 18:28:46.168 Entered conf registration check
D 18:28:46.181 Got a profile object
D 18:28:46.187 Got a conf object
D 18:28:46.187 Transaction failed No seats available
D 18:28:46.187 Leaving with exit value 0

Definition of method handling the API request 定义处理API请求的方法

   @ndb.transactional(xg=True) 
def _conferenceRegistration(self,confId):
    #get conference from id
    ret_val =True
    user = endpoints.get_current_user()
    if not user:
        raise endpoints.UnauthorizedException('Authorization required')
    user_id = getUserId(user)
    logging.debug('Entered conf registration check')
    p_key = ndb.Key(Profile, user_id)
    prof = p_key.get()

    logging.debug('Got a profile object')
    conf_key = ndb.Key(urlsafe=confId)
    conf = conf_key.get()
    logging.debug('Got a conf object')

    if conf and prof:
        if conf.seatsAvailable>0:
            logging.debug('Entered updating step')
            conf.seatsAvailable=conf.seatsAvailable-1
            time.sleep(25)
            prof.conferencesToAttend.append(conf.name)
            try:
                conf.put() 
            except TransactionFailedError:
                logging.debug('Transaction Failed error when trying to insert changes to conference')
                ret_val=False

            try:
                prof.put() 
            except TransactionFailedError:
                logging.debug('Transaction Failed error when trying to insert changes to profile')
                ret_val=False

            ret_val=True
        else:
            logging.debug('Transaction failed No seats available')
            ret_val=False  
    else:
        logging.debug('Could not get conf or profile instance')
        ret_val=False

    buf =  'Leaving with exit value %d' % (ret_val)   
    logging.debug(buf)
    return BooleanMessage(regSucc=ret_val)

This is expected. 这是预期的。 It isn't always the most efficient method for handling transactions, but it's the model they've chosen to use - it assumes that transaction collisions will be rare, and just verifies that before writing. 它并非始终是处理事务的最有效方法,但它是他们选择使用的模型-它假定事务冲突很少发生,并且仅在编写之前进行验证。 This is known as 'optimistic concurrency'. 这称为“乐观并发”。 The alternative woluld involve locking things, which can be quite complicated, and less efficient when transactions don't often collide. 另一种可能是涉及锁定事物,这很复杂,并且在事务不经常发生冲突时效率较低。

The documentation for how transactions work on appengine might help explain more, or there's a wikkipedia page on optimistic concurrency control 有关在 Appengine上进行交易的工作方式的文档可能会有助于解释更多信息,或者在乐观并发控制方面有wikkipedia页面

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM