Python 为什么查询会在 SQLAlchemy 中调用自动刷新?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32922210/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 12:28:35  来源:igfitidea点击:

Why does a query invoke a auto-flush in SQLAlchemy?

pythonsqlpython-3.xsqlalchemy

提问by buhtz

The code you see above is just a sample but it works to reproduce this error:

您在上面看到的代码只是一个示例,但它可以重现此错误:

sqlalchemy.exc.IntegrityError: (raised as a result of Query-invoked autoflush; 
consider using a session.no_autoflush block if this flush is occurring prematurely)
(sqlite3.IntegrityError) NOT NULL constraint failed: X.nn 
[SQL: 'INSERT INTO "X" (nn, val) VALUES (?, ?)'] [parameters: (None, 1)]

A mapped instance is still added to a session. The instance wants to know (which means query on the database) if other instances its own type exists having the same values. There is a second attribute/column (_nn). It is specified to NOT NULL. But by default it is NULL.

映射实例仍会添加到会话中。该实例想知道(这意味着在数据库上查询)是否存在其自己类型具有相同值的其他实例。还有第二个属性/列 ( _nn)。它被指定为NOT NULL。但默认情况下它是NULL.

When the instance (like in the sample) is still added to the session a call to query.one()invoke a auto-flush. This flush create an INSERTwhich tries to store the instance. This fails because _nnis still null and violates the NOT NULLconstraint.

当实例(如示例中)仍被添加到会话时,query.one()调用自动刷新。此刷新创建一个INSERT尝试存储实例的对象。这将失败,因为_nn它仍然为 null 并且违反了NOT NULL约束。

That is what I understand currently. But the question is why does it invoke an auto-flush? Can I block that?

这是我目前的理解。但问题是为什么它会调用自动刷新?我可以阻止吗?

#!/usr/bin/env python3

import os.path
import os
import sqlalchemy as sa 
import sqlalchemy.orm as sao
import sqlalchemy.ext.declarative as sad
from sqlalchemy_utils import create_database

_Base = sad.declarative_base()
session = None


class X(_Base):
    __tablename__ = 'X'

    _oid = sa.Column('oid', sa.Integer, primary_key=True)
    _nn = sa.Column('nn', sa.Integer, nullable=False) # NOT NULL!
    _val = sa.Column('val', sa.Integer)

    def __init__(self, val):
        self._val = val

    def test(self, session):
        q = session.query(X).filter(X._val == self._val)
        x = q.one()
        print('x={}'.format(x))

dbfile = 'x.db'

def _create_database():
    if os.path.exists(dbfile):
        os.remove(dbfile)

    engine = sa.create_engine('sqlite:///{}'.format(dbfile), echo=True)
    create_database(engine.url)
    _Base.metadata.create_all(engine)
    return sao.sessionmaker(bind=engine)()


if __name__ == '__main__':
    session = _create_database()

    for val in range(3):
        x = X(val)
        x._nn = 0
        session.add(x)
    session.commit()

    x = X(1)
    session.add(x)
    x.test(session)

Of course a solution would be to notadd the instance to the session before query.one()was called. This work. But in my real (but to complex for this question) use-case it isn't a nice solution.

当然,解决方案是在调用之前将实例添加到会话query.one()中。这项工作。但是在我真实的(但对于这个问题来说很复杂)用例中,这不是一个很好的解决方案。

采纳答案by Palasaty

How to turn off autoflushfeature:

如何关闭自动刷新功能:

  • Temporary: you can use no_autoflushcontext manager on snippet where you query the database, i.e. in X.testmethod:

    def test(self, session):
        with session.no_autoflush:
            q = session.query(X).filter(X._val == self._val)
            x = q.one()
            print('x={}'.format(x))
    
  • Session-wide: just pass autoflush=Falseto your sessionmaker:

    return sao.sessionmaker(bind=engine, autoflush=False)()
    
  • 临时:您可以在查询数据库的代码段上使用no_autoflush上下文管理器,即在X.test方法中:

    def test(self, session):
        with session.no_autoflush:
            q = session.query(X).filter(X._val == self._val)
            x = q.one()
            print('x={}'.format(x))
    
  • 会话范围:只需传递autoflush=False给您的会话创建者:

    return sao.sessionmaker(bind=engine, autoflush=False)()