为了理解ndb.transaction的内部工作原理,我尝试了以下实验。
Udacity示例中的会议API用于创建仅有一个席位的会议。
我在处理registerForConference API的方法中添加了一个wait,如下所示。我将日志调试消息放入了解流程,如下所示
我开始一个接一个地调用registerForConference API。
如果没有ndb.transaction,两者都将返回true(即返回成功注册)。使用ndb.transaction,第二个将返回false。因此,事情正在按预期进行。
但第二次请求所经历的步骤顺序是出乎意料的。我期待第二个查询在某个时刻陷入困境,直到第一个查询完成,并且在尝试插入时将抛出 TransactionFailedError 。相反,看起来第二个请求实际上是通过该方法一次,然后在第一个完成后重新执行该方法。在第二个例外中,它读取seatAvailable的更新值并确定它无法注册。这是预期的行为吗?如果是的话,这不是浪费,因为有些步骤可以并行完成,只有第一次查询完成后才需要执行冲突步骤吗?
打印的调试日志消息序列
/_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
处理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)
答案 0 :(得分:1)
这是预期的。它并不总是处理事务的最有效方法,但它是他们选择使用的模型 - 它假定事务冲突很少,并且只是在写入之前验证。这被称为“乐观并发”。替代woluld涉及锁定事物,这可能非常复杂,并且在事务不经常发生冲突时效率较低。
how transactions work on appengine的文档可能有助于解释更多内容,或者optimistic concurrency control上有wikkipedia页面