大道至简,新一代企业应用无栈开发

平台之上,一种语言,可视化、脚本化、全端一体化开发

事务处理

docutils document without title

数据库事务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.3   串行执行

如果并发执行,容易出现冲突,可以通过异步队列,串行执行。

  1. 在系统控制台,设置定制队列custom1线程数为1
  2. 调用 root.call_script_async 来调用

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