我在 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 驱动程序有几个长期存在的错误,这些错误会影响其事务行为的正确性。在其默认操作模式下,SERIALIZABLE 隔离、事务 DDL 和 SAVEPOINT 支持等 SQLite 特性是无效的,为了使用这些特性,必须采取变通方法。
问题本质上是驱动程序试图猜测用户的意图,未能启动事务,有时会过早结束它们,以尽量减少 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 语句很有帮助(这在上面的示例中使用):
SQLAlchemy 发送到数据库的调试(显示)SQL 命令