python - SQLAlchemy 和 sqlite 的嵌套事务

标签 python sqlite sqlalchemy python-elixir

我正在使用 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 支持保存点(http://www.sqlite.org/lang_savepoint.html)。

如何使嵌套事务起作用?

最佳答案

我在 Windows 上使用 Python 3 使用嵌套事务时遇到过这个问题。我使用的是 SQLite 版本 3.8.11,因此应该支持 SAVEPOINT。显然安装 pysqlite 不适合我,因为它不支持 Python 3。

在用头撞 table 几个小时后,我在文档中看到了这个部分:

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

In the section Database Locking Behavior / Concurrency, we refer to the pysqlite driver’s assortment of issues that prevent several features of SQLite from working correctly. The pysqlite DBAPI driver has several long-standing bugs which impact the correctness of its transactional behavior. In its default mode of operation, SQLite features such as SERIALIZABLE isolation, transactional DDL, and SAVEPOINT support are non-functional, and in order to use these features, workarounds must be taken.

The issue is essentially that the driver attempts to second-guess the user’s intent, failing to start transactions and sometimes ending them prematurely, in an effort to minimize the SQLite databases’s file locking behavior, even though SQLite itself uses “shared” locks for read-only activities.

SQLAlchemy chooses to not alter this behavior by default, as it is the long-expected behavior of the pysqlite driver; if and when the pysqlite driver attempts to repair these issues, that will be more of a driver towards defaults for SQLAlchemy.

The good news is that with a few events, we can implement transactional support fully, by disabling pysqlite’s feature entirely and emitting BEGIN ourselves. This is achieved using two event listeners:

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

关于python - SQLAlchemy 和 sqlite 的嵌套事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1654857/

相关文章:

python - dask dataframe 读取 parquet 架构差异

python - 如何修复 'BlobService' 未定义'错误

swift - 将任意长度的数组绑定(bind)到 SQLite 查询

python - Seaborn:标题和副标题放置

Android:在事务期间可以从 SQLite 数据库中抛出异常吗?

C GUI 程序在运行时崩溃

python - 使用 SQLAlchemy 预加载一组对象时出现问题

python - 带有 postgresql 的 SQLAlchemy 产生错误 "Could not locate column in row for column ' attype'"

python - FastApi 获取请求显示验证错误

python - python 中的 Pandas 数据框 : Removing rows from df1 based on rows in df2