[英]Multiple SQLAlchemy subquery loads are slow
我目前正在构建一个博客类型的应用,其中许多用户可以发表很多帖子。 为此,我在Flask中构建了以下模型:
class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(20), nullable=False, index=True)
lastname = db.Column(db.String(20), nullable=False, index=True)
email = db.Column(db.String(20), unique=True)
# many-to-many
contributions = db.relationship("Contributions", backref="user", lazy=True)
class Contributions(db.Model):
contribution_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey("user.user_id"), nullable=False
)
article_id = db.Column(
db.Integer, db.ForeignKey("article.article_id"), nullable=False
)
author_number = db.Column(db.Integer)
claimed = db.Column(db.Boolean, default=False)
class Article(db.Model):
title = db.Column(db.String(20), nullable=False)
article_id = db.Column(db.Integer, nullable=False, primary_key=True)
pubdate = db.Column(
db.DateTime(20), nullable=False, default=datetime.utcnow
)
contributions = db.relationship(
"Contributions", backref="article", lazy=True
)
def __repr__(self):
return f"Article title: {self.title}"
其中,每个贡献表示一个用户已对一篇文章进行了贡献(每个贡献都具有比用户/文章链接更多的信息)。
现在,我想为每个用户显示他们贡献的文章以及对该文章贡献的所有其他用户。 例如:
for article in articles:
for contribution in article:
authors += str(contrib.user.firstname + " " + contrib.user.lastname)
authors += str(", " + contrib.user.firstname + " " + contrib.user.lastname)
但是,此循环花费了很长时间,因为我在模型中启用了lazy = True。 因此,我尝试子查询加载:
contributions = (
User.query.options(
subqueryload("contributions.article.contributions")
)
.filter_by(
lastname=current_user.lastname,
firstname=current_user.firstname,
email=None,
)
.first()
.contributions
)
但对于每个User.query
,我只能subqueryload
要么contributions.article
或contributions.article.contributions
。 无论我做哪一个,另一个都要花很长时间才能下载。 有人对提高速度有什么建议吗?
您已声明:
...对于每个我要显示他们贡献的文章的用户,以及所有其他对该文章贡献的用户...
该查询:
contributions = (
User.query.options(
subqueryload("contributions.article.contributions")
)
.filter_by(
lastname=current_user.lastname,
firstname=current_user.firstname,
email=None,
)
.first()
.contributions
)
...不是实现您所陈述目标的特别有效的方法。 主要问题是,您最初是在通过current_user
代理查询已经拥有的User
。
...对于每个我要显示他们贡献的文章的用户...
然后,您应该查询用户的Article
对象:
articles = (
Article.query.join(Contributions)
.filter(Contributions.user == current_user)
.all()
)
...以及为该文章做出过贡献的所有其他用户...
问题在于,当我们访问每篇文章的contributions
属性时,我们仍然会发出一个查询,以获取该文章的所有贡献者,并且取决于文章的数量,这可能是很多额外的查询!
在这里,您可以查看查询日志。 第一个查询查找current_user
所有文章。 我已经标记了访问查询返回的第一篇文章的contributions
属性的位置,以便您可以看到为支持该属性访问而发出的查询:
2019-08-27 16:00:21,317 INFO sqlalchemy.engine.base.Engine
SELECT article.title AS article_title,
article.article_id AS article_article_id,
article.pubdate AS article_pubdate
FROM article INNER JOIN contributions
ON article.article_id = contributions.article_id
WHERE %(param_1)s = contributions.user_id
2019-08-27 16:00:21,318 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
************************ access article contributions here************************
2019-08-27 16:00:21,321 INFO sqlalchemy.engine.base.Engine
SELECT contributions.contribution_id AS contributions_contribution_id,
contributions.user_id AS contributions_user_id,
contributions.article_id AS contributions_article_id,
contributions.author_number AS contributions_author_number,
contributions.claimed AS contributions_claimed
FROM contributions
WHERE %(param_1)s = contributions.article_id
2019-08-27 16:00:21,332 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
因此,为避免这种情况,让我们热切要求Article
的贡献:
articles = (
Article.query.join(Contributions)
.filter(Contributions.user == current_user)
.options(subqueryload('contributions'))
.all()
)
这是相同的日志,但指定了紧急负载后:
2019-08-27 16:00:21,317 INFO sqlalchemy.engine.base.Engine
SELECT article.title AS article_title,
article.article_id AS article_article_id,
article.pubdate AS article_pubdate
FROM article INNER JOIN contributions
ON article.article_id = contributions.article_id
WHERE %(param_1)s = contributions.user_id
2019-08-27 16:00:21,318 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
2019-08-27 16:27:00,874 INFO sqlalchemy.engine.base.Engine
SELECT contributions.contribution_id AS contributions_contribution_id,
contributions.user_id AS contributions_user_id,
contributions.article_id AS contributions_article_id,
contributions.author_number AS contributions_author_number,
contributions.claimed AS contributions_claimed, anon_1.article_article_id AS anon_1_article_article_id
FROM (SELECT article.article_id AS article_article_id
FROM article INNER JOIN contributions
ON article.article_id = contributions.article_id
WHERE %(param_1)s = contributions.user_id)
AS anon_1 INNER JOIN contributions
ON anon_1.article_article_id = contributions.article_id
ORDER BY anon_1.article_article_id
2019-08-27 16:27:00,875 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
************************ access article contributions here************************
注意,现在在捐款属性访问之后没有查询。
因此,下一步是确保与每个文章的每个贡献相关联的用户都渴望被加载。 如果文章只有一个贡献者current_user
,则该用户应该已经在身份映射中加载,并且除非您先前已提交提交,否则该用户不应过期,因此在这种情况下,无需发出查询即可获取该用户。用户。 但是,如果贡献者不止一个,那么除了current_user
以外的任何贡献者都会触发查询。 因此,我想您可以考虑是否需要根据一般情况是文章大多只有一位作者还是多位来衡量。 假设你做..
从文档 :
加载程序选项也可以使用方法链接来“链接”,以指定如何在更深层次进行加载:
这就是我们要在此处完成的工作,一个遍历关系层次结构几个级别的热切负载,因此我们链接了这些热切负载选项。
articles = (
Article.query.join(Contributions)
.filter(Contributions.user == current_user)
.options(
subqueryload("contributions")
.joinedload("user", innerjoin=True)
)
.all()
)
这会向数据库层发出此查询:
2019-08-27 16:00:21,317 INFO sqlalchemy.engine.base.Engine
SELECT article.title AS article_title,
article.article_id AS article_article_id,
article.pubdate AS article_pubdate
FROM article INNER JOIN contributions
ON article.article_id = contributions.article_id
WHERE %(param_1)s = contributions.user_id
2019-08-27 16:00:21,318 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
2019-08-27 17:01:14,144 INFO sqlalchemy.engine.base.Engine
SELECT contributions.contribution_id AS contributions_contribution_id,
contributions.user_id AS contributions_user_id,
contributions.article_id AS contributions_article_id,
contributions.author_number AS contributions_author_number,
contributions.claimed AS contributions_claimed,
anon_1.article_article_id AS anon_1_article_article_id,
user_1.user_id AS user_1_user_id,
user_1.firstname AS user_1_firstname,
user_1.lastname AS user_1_lastname,
user_1.email AS user_1_email
FROM (SELECT article.article_id AS article_article_id
FROM article INNER JOIN contributions
ON article.article_id = contributions.article_id
WHERE %(param_1)s = contributions.user_id)
AS anon_1 INNER JOIN contributions
ON anon_1.article_article_id = contributions.article_id INNER JOIN user AS user_1 ON user_1.user_id = contributions.user_id
ORDER BY anon_1.article_article_id
和这样的for循环:
for article in articles:
for contribution in article.contributions:
print(contribution.user)
...不再发出其他查询。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.