Почему этот пример SQLAlchemy фиксирует изменения в БД?
Автор вопроса: FMcОтветов: 5
Этот пример иллюстрирует загадку, с которой я столкнулся в приложении, которое я создаю. Приложение должно поддерживать опцию, позволяющую пользователю выполнять код без фактического внесения изменений в БД. Однако, когда я добавил эту опцию, я обнаружил, что изменения сохранялись в БД, даже если я не вызывал метод commit().
Мой конкретный вопрос можно найти в комментариях к коду. Основная цель состоит в том, чтобы иметь более четкое представление о том, когда и почему SQLAlchemy будет фиксироваться в БД.
Мой более широкий вопрос заключается в том, должно ли мое приложение (а) использовать глобальный экземпляр Session или (б) использовать глобальный класс Session, из которого будут создаваться экземпляры конкретных экземпляров. Основываясь на этом примере, я начинаю думать, что правильный ответ (б). Это правильно? Изменить: эта документация SQLAlchemy предполагает, что (b) рекомендуется.
import sys
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
name = Column(String)
age = Column(Integer)
def __init__(self, name, age = 0):
self.name = name
self.age = 0
def __repr__(self):
return "<User(name='{0}', age={1})>".format(self.name, self.age)
engine = create_engine('sqlite://', echo = False)
Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
global_session = Session() # A global Session instance.
commit_ages = False # Whether to commit in modify_ages().
use_global = True # If True, modify_ages() will commit, regardless
# of the value of commit_ages. Why?
def get_session():
return global_session if use_global else Session()
def add_users(names):
s = get_session()
s.add_all(User(nm) for nm in names)
s.commit()
def list_users():
s = get_session()
for u in s.query(User): print ' ', u
def modify_ages():
s = get_session()
n = 0
for u in s.query(User):
n += 10
u.age = n
if commit_ages: s.commit()
add_users(('A', 'B', 'C'))
print '\nBefore:'
list_users()
modify_ages()
print '\nAfter:'
list_users()
Я попытался ответить на ваш конкретный вопрос, но да, в отношении вашего Правка: я бы определенно пошел по пути (b). Экземпляр глобального сеанса просто запрашивает проблемы, точно такие же, как этот. snapshoe 26.10.2010
tl;dr - Обновления не на самом деле зафиксированы в базе данных - они являются частью незафиксированной транзакции.
Я внес 2 отдельных изменения в ваш вызов create_engine(). (Кроме этой одной строки, я использую ваш код точно так, как он опубликован.)
Первый был
engine = create_engine('sqlite://', echo = True)
Это дает некоторую полезную информацию. Я не собираюсь публиковать здесь весь вывод, но обратите внимание, что никакие команды обновления SQL не выдаются до после второго вызова list_users():
...
After:
xxxx-xx-xx xx:xx:xx,xxx INFO sqlalchemy.engine.base.Engine.0x...d3d0 UPDATE users SET age=? WHERE users.id = ?
xxxx-xx-xx xx:xx:xx,xxx INFO sqlalchemy.engine.base.Engine.0x...d3d0 (10, 1)
...
Это признак того, что данные не сохраняются, а хранятся в объекте сеанса.
Второе изменение, которое я сделал, состояло в том, чтобы сохранить базу данных в файл с
Однако, если вы теперь откроете базу данных, которую мы только что создали, и запросите ее содержимое, вы увидите, что добавленные пользователи были сохранены в базе данных, а возрастные модификации - нет:
$ sqlite3 db.sqlite "select * from users"
1|A|0
2|B|0
3|C|0
Таким образом, второй вызов list_users() получает значения из объекта сеанса, а не из базы данных, поскольку выполняется транзакция, которая еще не была зафиксирована. Чтобы доказать это, добавьте следующие строки в конец вашего скрипта:
s = get_session()
s.rollback()
print '\nAfter rollback:'
list_users()
Ответ 2 · рейтинг 1 · Nathan Davis
Поскольку вы заявляете, что на самом деле используете MySQL в системе, в которой вы видите проблему, проверьте тип механизма, с которым была создана таблица. По умолчанию используется MyISAM, который не поддерживает ACID-транзакции. Убедитесь, что вы используете движок InnoDB, который выполняет ACID-транзакции.
Вы можете увидеть, какой движок использует таблица с
show create table users;
Вы можете изменить механизм базы данных для таблицы с помощью alter table:
alter table users engine="InnoDB";
Ответ 3 · рейтинг 1 · van
1. пример: Просто чтобы убедиться, что (или проверить, что) сеанс не фиксирует изменения, достаточно вызвать expunge_all в объекте сеанса. Скорее всего, это докажет, что изменения не на самом деле зафиксированы:
2. mysql: Как вы уже упоминали, пример sqlite может не отражать то, что вы на самом деле видите при использовании mysql. Как описано в sqlalchemy - MySQL - Механизмы хранения. , наиболее вероятной причиной вашей проблемы является использование нетранзакционных механизмов хранения (таких как MyISAM), что приводит к режиму выполнения autocommit.
3. область действия сеанса: хотя наличие одного глобального сеанса звучит как поиск проблемы, использование нового сеанса для каждого крошечного запроса также не является хорошей идеей. Сеанс следует рассматривать как транзакцию/единицу работы. Я считаю, что использование контекстных сеансов лучший из двух миров, где вам не нужно передавать объект сеанса в иерархии вызовов методов, и в то же время вам предоставляется довольно неплохая безопасность в многопоточной среде. Время от времени я использую сеанс local, когда знаю, что не хочу взаимодействовать с текущей транзакцией (сеансом).
Ответ 4 · Paulo Scardine
Обратите внимание, что значения по умолчанию для create_session() противоположны значениям для sessionmaker(): autoflush и expire_on_commit равны False, autocommit - True.
Ответ 5 · Scott
global_session уже создается, когда вы вызываете modify_ages(), и вы уже зафиксировали в базе данных. Если вы повторно создаете экземпляр global_session после фиксации, он должен начать новую транзакцию.
Я предполагаю, что, поскольку вы уже зафиксировали и повторно используете один и тот же объект, каждая дополнительная модификация фиксируется автоматически.