代码之家  ›  专栏  ›  技术社区  ›  FMc TLP

为什么这个SQLAlchemy示例将更改提交给DB?

  •  4
  • FMc TLP  · 技术社区  · 14 年前

    这个例子说明了我在构建应用程序时遇到的一个谜。应用程序需要支持一个选项,允许用户在不实际向数据库提交更改的情况下运行代码。但是,当我添加这个选项时,我发现即使没有调用 commit() 方法。

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

    我更广泛的问题是我的应用程序是否应该(a)使用全局 Session 实例,或(b)使用全局 类,从中实例化特定实例。根据这个例子,我开始认为正确答案是(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()
    
    5 回复  |  直到 14 年前
        1
  •  5
  •   snapshoe    14 年前

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


    我对您的create_engine()调用做了两个单独的更改。(除了这一行,我使用的是你发布的代码。)

    第一个是

    engine = create_engine('sqlite://', echo = True)
    

    之后 第二次调用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)
    ...
    

    这表明数据不是持久化的,而是保存在session对象中。

    我做的第二个更改是将数据库持久化为

    engine = create_engine('sqlite:///db.sqlite', echo = True)
    

    <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()
    
        2
  •  1
  •   Nathan Davis    14 年前

    由于您声明在系统上实际使用的是MySQL,因此您看到了问题,请检查创建表时使用的引擎类型。默认值是MyISAM,它不支持ACID事务。确保您使用的是InnoDB引擎,它执行ACID事务。

    您可以看到一个表使用的是哪种引擎

    show create table users;
    

    可以使用alter table更改表的db引擎:

    alter table users engine="InnoDB";
    
        3
  •  1
  •   van    14 年前

    一。例如: 为了确保(或检查)会话不提交更改,只需调用 expunge_all 在会话对象上。这很可能证明这些变化 实际承诺:

    ....
    print '\nAfter:'
    get_session().expunge_all()
    list_users()
    

    2。mysql数据库: sqlite 示例可能无法反映使用时实际看到的内容 mysql . 如文件所述 sqlalchemy - MySQL - Storage Engines ,出现问题的最可能原因是使用了非事务性存储引擎(如 MyISAM ),这将导致 autocommit 执行方式。

    三。会话范围: 寻找问题 unit-of-work . 我发现 contextual sessions 两个世界中最好的一个,在方法调用的层次结构中不需要传递session对象,同时在多线程环境中获得了相当好的安全性。我用的是 地方的 会话,我知道我不想与当前运行的事务(会话)交互。

        4
  •  0
  •   Paulo Scardine    14 年前

    注意,create_session()的默认值与sessionmaker()相反:autoflush和expire_on_commit为False,autocommit为True。

        5
  •  0
  •   Scott    14 年前

    我的猜测是,因为您已经提交并且正在使用同一个对象,所以每个额外的修改都会自动提交。