Два вложенных сеанса в Sqlalchemy
У меня есть внешний глобальный сеанс (через Flask-SqlAlchemy), а затем внутри функции я создаю другой сеанс, который фиксирует некоторые данные в базе данных (бэкэнд mariadb). Однако данные недоступны из внешнего сеанса, пока они не будут закрыты.
Пример:
db = SqlAlchemy()
def func():
s = db.Session()
with s.no_autoflush:
obj = models.MyObj(var="test")
s.add(obj)
# This inner session is needed because we can't do commits
# in this session at this point, but still do some inserts
# via outer session (db.session).
# Finally we commit inner session to database.
s.commit()
# This assertion will fail because data is not accessible
# in outer session.
# db.session.close() here would help, but it is not desirable
assert db.session.query(MyObj).filter_by(var="test").first()
# -> this fails.
Как я могу создать внутренний сеанс так, чтобы он был в той же транзакции, что и внешний сеанс (db.session), и, таким образом, данные, зафиксированные во внутреннем сеансе, были бы доступны во внешнем сеансе?
Обновить:
Вот минимально полный и проверяемый пример, надеюсь, он лучше объясняет проблему. Колба / колба-sqlalchemy не нужна.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = sa.create_engine('mysql+mysqldb://user:password@mariadb/mydatabase')
#engine = sa.create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
global_session = Session()
Model = declarative_base()
class MyTable(Model):
__tablename__ = 'mytable'
id = sa.Column(sa.Integer, primary_key=True)
var = sa.Column(sa.String(length=255), unique=True, nullable=False)
def func():
internal_session = Session()
with internal_session.no_autoflush:
# We add objects to internal_session, but we can't flush them yet (their linkage etc.
# will be built gradually here)
obj = MyTable(var="test")
internal_session.add(obj)
# At the same time we add some objects via global_session
obj2 = MyTable(var='test2')
global_session.add(obj2)
global_session.commit()
# If we perform any select query to global_session here, we will face problems later (at [*]).
# If we comment out this line [*] is fine.
assert not global_session.query(MyTable).filter_by(var='whatever!').first()
# Finally we commit inner session to database.
internal_session.commit()
# This assertion will fail because data is not accessible
# in outer session.
# global_session.close() # here would help, but it is not desirable
# [*]: this assertion will fail.
assert global_session.query(MyTable).filter_by(var='test').first()
if __name__ == '__main__':
try:
Model.metadata.drop_all(engine)
except:
pass
Model.metadata.create_all(engine)
func()
print('Ready')
1 ответ
Решение
Изменение уровня изоляции транзакции на READ COMMITTED
в global_session помогает.
В приведенном выше примере измените определение global_session на следующее:
global_session = Session(
bind=engine.execution_options(isolation_level='READ COMMITTED'))