SQLAlchemy的预加载/连接加载设置后可以被抑制吗?

21 投票
2 回答
13312 浏览
提问于 2025-04-16 09:25

我遇到了一种情况,通常情况下,物体之间的关系是这样的,提前配置一个“急切加载”(也就是把相关数据一起加载)是有意义的。不过现在我遇到了一个情况,我真的不想进行急切加载。

我应该把这个关系中的急切加载去掉,然后把所有相关的查询改成在查询的地方进行连接(这听起来很麻烦),还是说有没有什么方法可以在查询设置好之后,抑制急切加载呢?

下面是一个例子,展示了在用户和地址之间的关系上设置了急切加载。程序最后的查询能不能配置成不进行急切加载呢?

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
import sqlalchemy.orm as orm

##Set up SQLAlchemy for declarative use with Sqlite...
engine = sa.create_engine("sqlite://", echo = True)
DeclarativeBase = declarative_base()
Session = orm.sessionmaker(bind = engine)

class User(DeclarativeBase):
    __tablename__ = "users"
    id = sa.Column(sa.Integer, primary_key = True, autoincrement = True)
    name = sa.Column(sa.String, unique = True)
    addresses = orm.relationship("Address",
                                 lazy = "joined", #EAGER LOAD CONFIG IS HERE
                                 )
    def __init__(self, Name):
        self.name = Name

class Address(DeclarativeBase):
    __tablename__ = "addresses"
    id = sa.Column(sa.Integer, primary_key = True, autoincrement = True)
    address = sa.Column(sa.String, unique = True)
    FK_user = sa.Column(sa.Integer, sa.ForeignKey("users.id"))
    def __init__(self, Email):
        self.address = Email

##Generate data tables...
DeclarativeBase.metadata.create_all(engine)

##Add some data...
joe = User("Joe")
joe.addresses = [Address("joe@example.com"),
                 Address("joeyjojojs@example.net")]
s1 = Session()
s1.add(joe)
s1.commit()

## Access the data for the demo...
s2 = Session()

#How to suppress the eager load (auto-join) in the query below?
joe = s2.query(User).filter_by(name = "Joe").one() # <-- HERE?
for addr in joe.addresses:
    print addr.address

2 个回答

6

你可以使用 Query.options(raiseload('*'))Query.enable_eagerloads(False) 来控制数据加载的方式。

Query.enable_eagerloads(False) 这个设置会关闭查询中的所有急切加载。也就是说,即使你使用了 joinedload() 这样的指令,它也不会被执行。

Query.options(raiseload('*')) 则会在每一列上安装一个 raiseload 加载器,确保这些数据不会被懒加载:如果尝试懒加载,就会抛出一个异常。需要注意的是,这种模式适合开发和测试环境,但在生产环境中可能会造成问题。你可以像这样让它变得可选:

Query.options(raiseload('*') if development else defaultload([]))

另外要注意,raiseload('*') 只适用于顶层关系。它不会影响到连接的实体!如果你请求一个关系,你需要指定两次:

session.query(User).options(
    load_only('id'),
    joinedload(User.addresses).options(
        load_only('id'),
        raiseload('*')
    ),
    raiseload('*')
)

还有,raiseload('*') 只适用于关系,而不是列 :) 对于列,使用 defer(..., raiseload=True)

30

你可以根据每个查询的需要来调整属性的加载方式,记得我没记错的话。这样做可以行得通吗?

from sqlalchemy.orm import lazyload
joe = (s2.query(User)
    .options(lazyload('addresses'))
    .filter_by(name = "Joe").one())
for addr in joe.addresses:
    print addr.address

可以查看文档了解更多信息。

撰写回答