Вложенные транзакции с SQLAlchemy и sqlite
Я пишу приложение на Python, используя SQLAlchemy (и Elixir) с 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).
Как заставить работать вложенные транзакции?
3 ответа
Я столкнулся с этой проблемой, используя вложенные транзакции, используя Python 3 в Windows. Я использую SQLite версии 3.8.11, поэтому SAVEPOINT
должен быть поддержан. Очевидно, установка pysqlite для меня не вариант, так как он не поддерживает Python 3.
После нескольких часов ударов головой о стол я наткнулся на этот раздел в документации:
http://docs.sqlalchemy.org/en/latest/dialects/sqlite.html
В разделе "Поведение / параллелизм блокировки базы данных" мы ссылаемся на ряд проблем драйвера 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 (это используется в приведенном выше примере):
Отладка (отображение) команды SQL, отправленной в базу данных SQLAlchemy
Хотя sqlite, похоже, поддерживает вложенные транзакции через SAVEPOINT, это только версия 3.6.8, выпущенная 12 января 2009 года. 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. Я не могу сказать наверняка, что это решит вашу проблему, хотя, но я верю, что ответ объясняет, почему это не работает для вас сейчас.
SQLAlchemy использует pysqlite для взаимодействия с базой данных SQLite, если я не ошибаюсь, pysqlite по умолчанию свернет любой запрос, отправленный вами в транзакции.
Ответ может заключаться в правильной настройке уровня изоляции при подключении.