事务

一个 事务 将一个或多个 SQL 语句分组为一个单一的工作单元。要么所有语句都成功并提交到数据库,要么一个都没有成功——数据库回滚到事务开始之前的状态。

Peewee 以 自动提交模式 运行:在显式事务之外运行的每个语句都在其自己的隐式事务中运行。要分组语句,请使用本文档中描述的工具。

db.atomic

Database.atomic() 是推荐的事务 API。atomic() 可以用作上下文管理器或装饰器,并且它会自动处理嵌套。

如果在包裹的代码块中发生未处理的异常,当前代码块将被回滚。否则,语句将在代码块结束时提交。

作为上下文管理器

with db.atomic() as txn:
    user = User.create(username='charlie')
    tweet = Tweet.create(user=user, content='Hello')

# Both rows are committed when block exits normally.

作为装饰器

@db.atomic()
def create_user_with_tweet(username, content):
    user = User.create(username=username)
    Tweet.create(user=user, content=content)
    return user

如果未处理的异常从代码块中传播出来,事务(或保存点——见下文)将被回滚,并且异常会继续传播。

with db.atomic() as txn:
    User.create(username='huey')
    # User has been INSERTed into the database but the transaction is not
    # yet committed because we haven't left the scope of the "with" block.

    raise ValueError('something went wrong')
    # This exception is unhandled - the transaction will be rolled-back and
    # the ValueError will be raised.

# User('huey') was NOT committed, the transaction rolled-back.
# The ValueError is raised here.

手动提交 / 回滚

你可以在 atomic() 块内显式地提交或回滚。在调用 commit()rollback() 后,一个新的事务(或保存点)会自动开始。

with db.atomic() as txn:
    try:
        save_objects()
    except SaveError:
        txn.rollback()  # Roll back, new transaction starts automatically.
        log_error()

    finalize()  # Runs in a new transaction.

# finalize()'s changes are committed here.

嵌套事务

最外层的 atomic() 块会创建一个事务。任何嵌套的 atomic() 块都会创建 保存点。保存点是事务中的一个命名点,你可以在不影响事务其余部分的情况下回滚到该点。

with db.atomic():                        # Transaction begins.
    User.create(username='charlie')

    with db.atomic() as sp:              # Savepoint begins.
        User.create(username='huey')
        sp.rollback()                    # Rolls back huey only.
        User.create(username='alice')    # New savepoint begins here.

    User.create(username='mickey')
# Committed: charlie, alice, mickey. huey was rolled back.

保存点可以任意深度地嵌套

with db.atomic():
    with db.atomic():
        with db.atomic() as inner:
            do_something_risky()
            inner.rollback()   # Only the innermost work is lost.
        do_something_safe()

atomic() 在内部跟踪嵌套深度。你无需手动管理保存点名称或事务状态。

显式事务

Database.transaction() 打开一个不嵌套的显式事务。在外部 transaction() 块内的任何 transaction() 调用都会被忽略——只有最外层的事务是活动的。

仅当你明确需要一个扁平的、非嵌套的事务时才使用它。在大多数情况下,atomic() 是更好的选择。

如果在包裹的代码块中发生异常,事务将被回滚。否则,语句将在包裹的代码块结束时提交。

with db.transaction() as txn:
    User.create(username='mickey')
    txn.commit()         # Commit now; a new transaction begins.
    User.create(username='huey')
    txn.rollback()       # Roll back huey; a new transaction begins.
    User.create(username='zaizee')
# zaizee is committed when the block exits.

如果你尝试使用 transaction() 上下文管理器在 Peewee 中嵌套事务,则只会使用最外层的事务。

由于这可能导致不可预测的行为,建议你使用 atomic()

显式保存点

Database.savepoint() 在一个活动的事务中创建一个保存点。保存点必须发生在事务内部,但可以任意深度地嵌套。

with db.transaction() as txn:
    with db.savepoint() as sp:
        User.create(username='mickey')

    with db.savepoint() as sp2:
        User.create(username='zaizee')
        sp2.rollback()  # "zaizee" is not saved.
        User.create(username='huey')

# mickey and huey were created.

如果你手动提交或回滚一个保存点,一个新的保存点将自动开始。

自动提交模式

Peewee 要求底层驱动程序以自动提交模式运行并自行管理事务边界。这与 DB-API 2.0 的默认行为不同,后者会隐式启动一个事务并要求你手动提交。因此,Peewee 会将所有 DB-API 驱动程序置于 自动提交 模式。

在极少数情况下,如果你需要直接控制 BEGIN/COMMIT/ ROLLBACK——完全绕过 Peewee 的事务管理——请使用 manual_commit()

with db.manual_commit():
    db.begin()  # Begin transaction explicitly.
    try:
        user.delete_instance(recursive=True)
    except:
        db.rollback()  # Rollback! An error occurred.
        raise
    else:
        try:
            db.commit()  # Commit changes.
        except:
            db.rollback()
            raise

manual_commit 在代码块的持续时间内暂停 Peewee 的事务管理。atomic()transaction() 在其内部没有效果。这在应用程序代码中很少需要。

SQLite 事务锁定模式

SQLite 支持三种事务锁定模式。当需要精确控制读写锁定时尚可使用这些模式。

with db.atomic('EXCLUSIVE'):
    # No other connection can read or write until this commits.
    do_something()

@db.atomic('IMMEDIATE')
def load_data():
    # No other writer is allowed, but readers can proceed.
    insert_records()

这三种模式

  • DEFERRED (默认) - 在读写发生时获取最少的必要锁。另一个写入者可以在 BEGIN 和你的第一次写入之间介入。

  • IMMEDIATE - 在 BEGIN 时获取写入保留锁。其他写入者将被阻塞;读取者可以继续。

  • EXCLUSIVE - 在 BEGIN 时获取排他锁。在事务完成之前,没有其他连接可以读取或写入。

另请参阅

SQLite 锁定文档.

Postgresql 隔离级别注意事项

Postgresql 支持每个事务可配置的隔离级别,从最宽松到最严格

  • READ UNCOMMITTED (未提交读)

  • READ COMMITTED (已提交读) (大多数部署中的默认值)

  • REPEATABLE READ (可重复读)

  • SERIALIZABLE (串行化)

有关讨论,请参阅 Postgresql 事务隔离文档

默认隔离级别在初始化 PostgresqlDatabase 时指定

db = PostgresqlDatabase(
    'my_app',
    user='postgres',
    host='10.8.0.1',
    port=5432,
    isolation_level='SERIALIZABLE')

# Or use the constants provided by the driver.

from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE
db = PostgresqlDatabase(
    ...
    isolation_level=ISOLATION_LEVEL_SERIALIZABLE)


from psycopg import IsolationLevel
db = PostgresqlDatabase(
    ...
    isolation_level=IsolationLevel.SERIALIZABLE)

要控制事务的隔离级别,你可以将所需的设置传递给最外层的 atomic()

with db.atomic('SERIALIZABLE') as txn:
    ...

嵌套的 atomic() 块不能指定隔离级别。