2010-10-22 711 views
4

这个例子说明了我在构建的应用程序中遇到的一个谜题。应用程序需要支持一个选项,允许用户在不实际提交数据库更改的情况下执行代码。但是,当我添加此选项时,即使没有调用commit()方法,我也发现更改仍保留在数据库中。为什么此SQLAlchemy示例将更改提交给数据库?

我的具体问题可以在代码注释中找到。基本目标是更清楚地了解SQLAlchemy何时以及为什么要提交给数据库。

我的更广泛的问题是,我的应用程序应该(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

我试图回答您的具体问题,但是,是的,在参考你的**编辑:**,我肯定会去的(B)的路线。全局会话实例只是要求问题与此类似。 – snapshoe 2010-10-26 05:28:41

回答

5

tl; dr - 更新是而不是实际上已提交给数据库 - 它们是正在进行的未提交事务的一部分。


我对您对create_engine()的调用做了2个独立的更改。 (除了这一行,我用你的代码完全一样发布。)

首先是

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) 
... 

这是一个线索,该数据是没有坚持下去,而是继续留在会话对象中。

我做第二个变化是对数据库坚持到一个文件

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() 
+0

非常感谢。这看起来是正确的答案。不幸的是,这意味着我的示例脚本没有证明在我的完整应用程序中观察到的问题 - 即使我避免调用commit(),它实际上正在提交对MySQL DB的更改。时间做更多的调查! – FMc 2010-10-26 10:39:16

+0

对于create_engine调用,我会说'echo = True',而调试将是缩小发生什么时的最佳工具之一。 – snapshoe 2010-10-27 01:08:59

0

注意,建立事务()的默认值的那个sessionmaker的相反():自动冲洗和expire_on_commit都是假的,自动提交为True。

+0

感谢您的回复,但我没有按照如何解决这个问题。 – FMc 2010-10-23 00:50:43

+0

尝试sessionmaker()而不是create_session()。 – 2010-10-23 01:47:41

+0

据我所知,我已经在使用'sessionmaker()',而不是'create_session()'。我错过了什么吗? – FMc 2010-10-23 11:32:21

0

global_session在您调用modify_ages()时已经实例化并且您已经提交到数据库。如果您在提交后重新实例化global_session,它应该启动一个新的事务。

我的猜测是因为你已经提交并重新使用同一个对象,每个额外的修改都会自动提交。

1

既然你说出你实际上是使用MySQL,你所看到的问题的系统上,检查表创建时使用的发动机类型。默认值是MyISAM,它不支持ACID事务。确保你使用的InnoDB引擎,它确实做ACID事务。

可以看到哪些引擎表与

show create table users; 

使用您可以更改数据库引擎用alter table表:

alter table users engine="InnoDB"; 
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最好的两个世界,在这里你不必通过会话对象的方法调用的层次结构的使用情况,并在同一时间,你被赋予在多线程环境中一个非常良好的安全性。我做的,而在那里,我知道我不想与当前运行的事务(会议)后的互动使用当地会议。

+0

谢谢。这非常有帮助。 – FMc 2010-10-28 11:21:12

相关问题