事务处理
数据库事务transaction,能够保证数据操作的原子性,要么完全成功,要么失败完全回滚。 不会出现数据写一半,导致脏数据的情况。
1 系统默认的事务机制
用户每次从浏览器发起请求,系统就自动开启一个新的数据库事务,请求成功处理,自动提交数据库事务(commit);
一旦处理出现异常,会自动回滚事务(abort),所有变更都不会记录到数据库,防止出现脏写不一致。
通常发生异常不需要做处理,系统会自动提示错误信息。
2 异常处理
一旦发生异常,有时候需要主动捕获,进行特殊的异常提示。
这时候可能事务已经做了部分修改,如果简单try except可能导致数据库状态不一致:
# 错误的示例!! try: do_something() except: return view.message('do something wrong', 'error')
由于except捕获了异常,这个请求最后不会抛出异常,平台不会自动进行数据回滚。 这样最终可能do_something做了多次数据库操作,发生异常之前的操作会写入到数据库,导致数据脏写!
如果需要确保事务的完整性,通常需要引入事务处理:
# 正确的错误处理方案 try: do_something() except: import transaction transaction.abort() # 主动回滚 return view.message('do something wrong', 'error')
这样写起来有点啰嗦,我们可以直接抛出一个 LogicError 的异常,平台自动会提示错误信息:
try: do_something() except: raise LogicError('do something wrong')
3 内置的事务规则
正常写代码,完全无需考虑事务。平台数据操作自动支持事务处理的:
一个请求开始,平台自动进入事务:
transaction.begin()
请求成功完成结束,平台自动结束事务:
transaction.commit()
如果commit的时候,其他人已经修改,会放弃重试3次,仍然失败则抛出ConflictError。
如果有任何的其他python异常,事务自动回滚:
transaction.abort()
4 发生事务冲突
默认的事务处理,通常能够满足我们的需求。但是事务可能因为并发修改, 导致最后的事务提交失败。
一个事务耗时越长,修改的内容越多,越容易和其他事务发生冲突。
目前系统的事务管理,是低效的: 如果请求开始到结束直接,有其他请求提交修改,则会发生冲突 这个问题,在后续处理会改进。
5 冲突处理
5.1 默认处理
一旦发生冲突,系统默认会重试3次,三次失败然后回滚。 因此一旦发生冲突,系统性能会变得非常慢。
5.2 小事务避免冲突
所以,在编写代码的时候,一般需要尽量避免耗时长的事务,避免大量并发修改。
如果一个请求处理的时间很长,可以在开始做一些读和计算的工作,真正开始写入数据之前,重新开始事务:
import transaction transaction.begin()
这样事务从这里才开始,减少了事务的长度。
5.4 自己控制重试策略
由于默认的重试策略,会完整重试,因此再次冲突的可能性比较大。
可以将大的事务分割成多个小的事务,分别执行。
同时通过try/except监控冲突,如果发生冲突,对局部事务进行重新重试,注意每次重试需要执行 transaction.begin 。
典型代码:
max_attempts = 3 attempts = 0 while True: try: with transaction.manager: ... code that updates a database except transaction.interfaces.TransientError: attempts += 1 if attempts == max_attempts: raise else: break
参看:
http://www.zodb.org/en/latest/guide/transactions-and-threading.html#retrying-transactions