【问题标题】:Nested transactions with SQLAlchemy and sqlite使用 SQLAlchemy 和 sqlite 的嵌套事务
【发布时间】:2010-12-11 21:49:39
【问题描述】:

我正在使用 SQLAlchemy(和 Elixir)在 Python 中编写一个应用程序,并将 SQLite 作为数据库后端。我使用代码session.begin_transaction() 开始新事务,但是当我调用session.rollback() 时出现以下错误:

sqlalchemy.exceptions.OperationalError: (OperationalError) no such savepoint: sa_savepoint_1 u'ROLLBACK TO SAVEPOINT sa_savepoint_1' []

我在调用session.commit() 时也遇到了类似的错误。据我所知,sqlite 支持 SAVEPOINTS (http://www.sqlite.org/lang_savepoint.html)。

如何让嵌套事务工作?

【问题讨论】:

  • 如果您创建一个演示 sn-p 代码,我可以更轻松地回答这个问题。您可以使用 sqlite :memory: 数据库轻松做到这一点,它会创建一个内存数据库。

标签: python sqlite sqlalchemy python-elixir


【解决方案1】:

我在 Windows 上使用 Python 3 使用嵌套事务时遇到了这个问题。我使用的是 SQLite 版本 3.8.11,所以应该支持 SAVEPOINT。显然安装 pysqlite 对我来说不是一个选项,因为它不支持 Python 3。

在我的头撞在桌子上几个小时后,我在文档中看到了这个部分:

http://docs.sqlalchemy.org/en/latest/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl

在数据库锁定行为/并发部分,我们参考 pysqlite 驱动程序的各种问题,这些问题阻止了几个 SQLite 的功能无法正常工作。 pysqlite DBAPI 驱动程序 有几个长期存在的错误会影响其正确性 交易行为。在其默认操作模式下,SQLite 诸如 SERIALIZABLE 隔离、事务 DDL 和 SAVEPOINT 支持是非功能性的,为了使用这些 功能,必须采取变通方法。

问题本质上是司机试图猜测 用户的意图,未能开始交易,有时会结束交易 过早地,以尽量减少 SQLite 数据库的文件 锁定行为,即使 SQLite 本身使用“共享”锁定 只读活动。

SQLAlchemy 默认选择不改变这种行为,因为它是 pysqlite 驱动程序的长期预期行为;如果和当 pysqlite 驱动程序尝试修复这些问题,这将更多 SQLAlchemy 的默认驱动程序。

好消息是,通过一些事件,我们可以实现 通过完全禁用 pysqlite 的功能,完全支持事务 并自己发射 BEGIN。这是使用两个事件来实现的 听众:

from sqlalchemy import create_engine, event

engine = create_engine("sqlite:///myfile.db")

@event.listens_for(engine, "connect")
def do_connect(dbapi_connection, connection_record):
    # disable pysqlite's emitting of the BEGIN statement entirely.
    # also stops it from emitting COMMIT before any DDL.
    dbapi_connection.isolation_level = None

@event.listens_for(engine, "begin")
def do_begin(conn):
    # emit our own BEGIN
    conn.execute("BEGIN")

添加上面的监听器完全解决了我的问题!

我已经发布了一个完整的工作示例作为要点:

https://gist.github.com/snorfalorpagus/c48770e7d1fcb9438830304c4cca24b9

我还发现记录 SQL 语句很有帮助(这在上面的示例中使用):

Debugging (displaying) SQL command sent to the db by SQLAlchemy

【讨论】:

    【解决方案2】:

    虽然 sqlite 似乎确实支持通过 SAVEPOINT 的嵌套事务,但它仅在 version 3.6.8, released 2009 Jan 12 中支持。 Python,至少到 v2.6,使用早期版本:

    c:\svn\core\apps\general>python
    Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)] on win32
    >>> import sqlite3 as s
    >>> s.sqlite_version
    '3.5.9'
    

    我相信你可以自己安装PySqlite,最新的似乎支持v3.6.12。虽然我不能肯定这会解决你的问题,但我相信答案解释了为什么它现在不适合你。

    【讨论】:

    • sqlite3版本未绑定python版本
    • @iny,不太清楚你的意思是什么。 Python 的 sqlite 版本(即 Python 中包含的那个)当然绑定到特定版本的 sqlite。反过来说……好吧,我当然不会在上面这么说。
    • 似乎 Python 2.7 还附带了一个旧的 SQLite3 库。安装 PySqlite 为我解决了这个问题。
    • @KiranJonnalagadda 虽然 Python 2.7 可能没有最新版本,但它的版本不是高于 3.6.8 吗?在带有 Python 2.7.3 的 Ubuntu 12.04 上,我得到 sqlite v3.7.9,在带有 Python 2.7.5 的 Windows 上,我得到 sqlite v3.6.21。您在尝试 Python 2.7.2 或更早版本吗?
    • @PeterHansen 我在 2.7.8。
    【解决方案3】:

    SQLAlchemy 使用 pysqlite 与 SQLite 数据库进行交互,如果我没记错的话,pysqlite 默认情况下会包装您在事务中发送的任何查询。

    答案可能在于在连接时正确设置隔离级别。

    Some discussion about that over here

    【讨论】:

    • 我想要嵌套事务,您可以在其中回滚而不会丢失外部事务中已更改的任何内容。我不确定 pysqlite 是否可以自动执行此操作。您是在建议无法使用 SAVEPOINTS,还是只是建议另一种(可能的)方法来解决问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-21
    • 1970-01-01
    • 2015-07-01
    相关资源
    最近更新 更多