查询添加到SQLAlchemy

时间:2018-01-13 13:41:37

标签: python transactions sqlalchemy

所以我在没有太多背景的情况下提出了这个问题,然后投了反对,让我们再试一次......

首先,我不遵循SQLAlchemy session.add背后的逻辑。我理解它将对象排队插入,我理解session.query在连接的数据库中而不是在会话中查找,但在SQLAlchemy中,它可以在不首先查询会话的情况下查询会话{{1} }}?我对来自session.flush的内容的期望是它会查询会话...

我现在在session.query session.new出来之后手动查看None

在我的session.query().first()之前,我不想做session.flush的两个原因,

  • 一个基于效率恐惧的问题(为什么我应该写入数据库,如果我仍然在用户可能想要回滚的会话中,则查询数据库?);
  • 两个是因为我采用了fairly large program,它设法定义了自己的session.query,其实例导致flush也提交。

所以这个问题的核心是谁帮助我在github上的GPL程序中找到错误!

这是一个在bauble / ghini中出现令人惊讶的行为的代码片段:

Session

这里的输出是:

# setting up things in ghini
# <replace-later>
import bauble
import bauble.db as db
db.open('sqlite:///:memory:', verify=False)
from bauble.prefs import prefs
import bauble.pluginmgr as pluginmgr
prefs.init()
prefs.testing = True
pluginmgr.load()
db.create(True)
Session = bauble.db.Session
from bauble.plugins.garden import Location
# </replace-later>

# now just plain straightforward usage
session = Session()

session.query(Location).delete()
session.commit()
u0 = session.query(Location).filter_by(code=u'mario').first()
print u0

u1 = Location(code=u'mario')
session.add(u1)
session.flush()

u2 = session.query(Location).filter_by(code=u'mario').one()
print u1, u2, u1==u2

session.rollback()
u3 = session.query(Location).filter_by(code=u'mario').first()
print u3

这里你有我认为只是标准的简单代码来设置数据库:

None
mario mario True
mario

有了这个,上面相同代码片段的输出就不那么令人惊讶了:

from sqlalchemy import Column, Unicode

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

class Location(Base):
    __tablename__ = 'location'
    code = Column(Unicode(64), index=True, primary_key=True)
    def __init__(self, code=None):
        self.code = code
    def __repr__(self):
        return self.code

from sqlalchemy import create_engine
engine = create_engine('sqlite:///joindemo.db')

Base.metadata.create_all(engine)

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine, autoflush=False)

1 个答案:

答案 0 :(得分:2)

bauble中的刷新最终发出COMMIT的原因是line 133 in db.py处理历史记录表的原因:

table.insert(dict(table_name=mapper.local_table.name,
                  table_id=instance.id, values=str(row),
                  operation=operation, user=user,
                  timestamp=datetime.datetime.today())).execute()

不是使用传入的事务连接在事件处理程序中发出附加SQL,而是,因为它们应该,而是按原样执行语句,这意味着它最终使用引擎作为绑定(通过表格的元数据找到)。使用引擎执行具有autocommit行为。由于bauble always uses一个SingletonThreadPool,每个线程只有一个连接,因此该语句最终也会提交刷新的更改。我想知道这个错误是否是为什么bauble禁用autoflush ...

修复方法是更改​​事件处理以使用事务连接:

class HistoryExtension(orm.MapperExtension):
    """
    HistoryExtension is a
    :class:`~sqlalchemy.orm.interfaces.MapperExtension` that is added
    to all clases that inherit from bauble.db.Base so that all
    inserts, updates, and deletes made to the mapped objects are
    recorded in the `history` table.
    """
    def _add(self, operation, mapper, connection, instance):
        """
        Add a new entry to the history table.
        """
        ...  # a ton of code here
        table = History.__table__
        stmt = table.insert(dict(table_name=mapper.local_table.name,
                                 table_id=instance.id, values=str(row),
                                 operation=operation, user=user,
                                 timestamp=datetime.datetime.today()))
        connection.execute(stmt)

    def after_update(self, mapper, connection, instance):
        self._add('update', mapper, connection, instance)

    def after_insert(self, mapper, connection, instance):
        self._add('insert', mapper, connection, instance)

    def after_delete(self, mapper, connection, instance):
        self._add('delete', mapper, connection, instance)

值得注意的是,自版本0.7以来MapperExtension已被弃用。

关于你对会话的看法,我引用了"Session Basics",你真正应该阅读:

  

在最一般意义上,Session建立与数据库的所有对话,并代表您在其生命周期内加载或与之关联的所有对象的“保留区域”。它提供了获取 Query对象的入口点,该对象使用Session对象的当前数据库连接向数据库发送查询,...

"Is the Session a cache?"

  

... Yeee没有。它有点用作缓存,因为它实现了身份映射模式,并存储了键入其主键的对象。但是,它不执行任何类型的查询缓存。这意味着,如果你说session.query(Foo).filter_by(name='bar'),即使Foo(name='bar')就在那里,在身份地图中,会话也不知道。它必须向数据库发出SQL ,获取行,然后当它看到行中的主键时,它可以查看本地身份映射并查看该对象已经存在。只有当您query.get({some primary key})Session不必发出查询时,才会这样做。

所以:

  

我期望读取session.query的内容是它查询会话...

你的期望是错误的。 Session处理与数据库的对话 - 在other things中。

  

在我的会话之前,我不想做session.flush的两个原因。查询,

     
      
  • 一个基于效率恐惧的问题(为什么我应该写入数据库,如果我仍然在用户可能想要回滚的会话中,则查询数据库?);
  •   

因为您的数据库可能会进行验证,拥有触发器并为某些列生成值 - 主键,时间戳等。您认为自己要插入的数据可能会导致数据库中出现其他内容,而Session绝对无法知道这一点。

此外,为什么SQLAlchemy本身应该使用自己的查询引擎实现一种内存数据库,以及同步2个数据库所带来的所有问题? SQLAlchemy如何支持您查询的不同数据库的所有不同操作和功能?您的简单等式谓词示例只是略微浮出水面。

当您回滚时,您将回滚数据库的事务(以及会话的未刷新更改)。

  
      
  • 两个是因为我已经采用了一个相当大的程序,它设法定义了自己的Session,其实例导致flush也提交。
  •   

由事件处理错误引起。

相关问题