这个例子说明了我在构建的应用程序中遇到的一个谜。应用程序需要支持一个允许用户在不实际向DB提交更改的情况下执行代码的选项。但是,当我添加此选项时,我发现即使我没有调用commit()
方法,更改仍保留在数据库中。
我的具体问题可以在代码注释中找到。基本目标是更清楚地了解SQLAlchemy何时以及为何将提交给DB。
我更广泛的问题是我的应用程序是应该(a)使用全局Session
实例,还是(b)使用全局Session
类,从中实例化特定实例。基于这个例子,我开始认为正确的答案是(b)。是对的吗? 修改:this SQLAlchemy documentation表示建议使用(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()
答案 0 :(得分:5)
tl; dr - 更新不实际上已提交给数据库 - 它们是正在进行的未提交事务的一部分。
我对您对create_engine()的调用进行了两次单独的更改。 (除了这一行之外,我完全按照发布的方式使用您的代码。)
第一个是
engine = create_engine('sqlite://', echo = True)
这提供了一些有用的信息。我不打算在这里发布整个输出,但是注意在第二次调用list_users()之后之前没有发出SQL更新命令:
...
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)
...
这是一个线索,数据不会持久存在,而是保存在会话对象中。
我做的第二个更改是将数据库保存到带有
的文件engine = create_engine('sqlite:///db.sqlite', echo = True)
再次运行脚本为第二次调用list_users()提供了与之前相同的输出:
<User(name='A', age=10)>
<User(name='B', age=20)>
<User(name='C', age=30)>
但是,如果您现在打开我们刚创建的数据库并查询其内容,您可以看到添加的用户被持久化到数据库,但年龄修改不是:
$ 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()
答案 1 :(得分:1)
由于您声明您实际上在系统上使用MySQL而遇到问题,请检查创建表的引擎类型。默认值为MyISAM,不支持ACID事务。确保您使用InnoDB引擎,该引擎执行ACID事务。
您可以通过
查看表格正在使用哪个引擎show create table users;
您可以使用alter table更改表的数据库引擎:
alter table users engine="InnoDB";
答案 2 :(得分:1)
<强> 1。示例:只是为了确保(或检查)会话是否未提交更改,只需在会话对象上调用expunge_all即可。这很可能证明这些变化不实际提交:
....
print '\nAfter:'
get_session().expunge_all()
list_users()
<强> 2。 mysql:正如您已经提到的,sqlite
示例可能无法反映您在使用mysql
时实际看到的内容。正如sqlalchemy - MySQL - Storage Engines中所述,问题的最可能原因是使用非事务性存储引擎(如MyISAM
),这导致autocommit
执行模式。
第3。会话范围:虽然有一个全局会话听起来像寻找问题,但是对每个微小的请求使用新会话也不是一个好主意。您应该将会话视为事务/ unit-of-work。我发现contextual sessions的用法是两个世界中最好的,你不必在方法调用的层次结构中传递会话对象,同时你在多个方面给予了相当好的安全性。线程环境。我偶尔会使用本地会话,我知道我不想与当前正在运行的事务(会话)进行交互。
答案 3 :(得分:0)
请注意,create_session()的默认值与sessionmaker()的默认值相反:autoflush和expire_on_commit为False,autocommit为True。
答案 4 :(得分:0)
global_session。如果在提交后重新实例化global_session,它应该启动一个新事务。
我猜是因为你已经提交并重新使用同一个对象,所以每个额外的修改都会自动提交。