lazy =“ dynamic”有什么问题?有哪些选择?

时间:2018-10-11 18:49:18

标签: python flask sqlalchemy flask-sqlalchemy

此问题已完全重写为10/17/18

为了拥有一个“ Edit Versioning System”(类似于StackOverflow的功能),我配置了以下类:

tags = db.Table(
    "tags",
    db.Column("tag_id", db.Integer, db.ForeignKey("tag.id")),
    db.Column("post_version_id", db.Integer,
        db.ForeignKey("post_version.id"))
    )

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tag = db.Column(db.String(128), index=True, unique=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    head_id = db.Column(db.Integer, db.ForeignKey("post_version.id"))

class PostVersion(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    previous_id = db.Column(db.Integer, db.ForeignKey("post_version.id"), default=None)
    pointer_id = db.Column(db.Integer, db.ForeignKey("annotation.id"))
    current = db.Column(db.Boolean, index=True)
    tags = db.relationship("Tag", secondary=tags)

不包括无关紧要的列,例如帖子的内容等。实际上,真正的数据模型是注释; 为了通用起见,我简化了这些模型

实际数据由136个Post组成,这些变量具有不同的标记并通过编辑进行版本控制;也就是说:我生成了136个Post。我有15个Tag。最初的136个Post被标记为与2个Tag一致。随后,我用不同的标签为Post标记了不同的标签(使用我的编辑系统;这样,对于经过编辑的PostVersion就有多个Post)。

您可能会注意到,Post和PostVersion之间有一个循环引用。为了进行实验,我使用它来配置以下两个关系:

关系1 posts

posts = db.relationship("Post",
    secondary="join(tags, PostVersion,"
        "tags.c.post_version_id==PostVersion.id)",
    primaryjoin="Tag.id==tags.c.tag_id",
    secondaryjoin="Post.head_id==PostVersion.id",
    lazy="dynamic")

基于SQL语句

SELECT
    post.id
FROM
    tag
JOIN
    tags ON tag.id=tags.tag_id
JOIN
    post_version ON tags.post_version_id=post_version.id
JOIN
    post ON post.head_id=post_version.id
WHERE
    tag.id=<tag_id>

关系2 posts2

posts2 = db.relationship("Post",
    secondary="join(tags, PostVersion,"
    "and_(tags.c.post_version_id==PostVersion.id,"
    "AnnotationVersion.current==True))",
    primaryjoin="Tag.id==tags.c.tag_id",
    secondaryjoin="PostVersion.pointer_id==Post.id",
    lazy="dynamic")

基于SQL语句

SELECT
    annotation.id
FROM
    tag
JOIN
    tags ON tag.id=tags.tag_id
JOIN
    annotation_version ON tags.annotation_version_id=annotation_version.id AND 
    annotation_version.current=1
JOIN
    annotation ON annotation_version.pointer_id = annotation.id
WHERE
    tag_id=8;

这将产生以下数据:

Tag         Actual      len(t.posts.all())  len(t.posts.paginate(1,5,False).items)
t1          0           0                   0
t2          1           136                 5
t3          1           136                 5
t8          136         136                 1
t14         136         136                 1
t15         24          136                 1

Tag         Actual      t.posts.count()     t.posts2.count()
t1          0           0                   0
t2          1           136                 163
t3          1           136                 163
t8          136         22168               26569
t14         136         22168               26569
t15         24          3264                3912

我排除了多余的标记(即所有其他Tag带有0个Post)和相同的数据(例如,来自posts2的结果与posts

您可以看到结果存在一些严重的问题!特别是对于这两种关系,如果lazy="dynamic"被关闭,则总是返回正确的Post

@IljaEverilä使用echo=True创建引擎时,发现lazy="dynamic"更改了SQL。我引用这个问题的评论:

  

简而言之:使用lazy="dynamic"可以得到FROM post, tags, post_version WHERE ...,但是如果没有FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id WHERE ....则可以看到,动态设置几乎忽略了复合辅助设备。现在的问题是“为什么?”


我的问题:

1。这是一个错误吗?

2。我该怎么办才能纠正这种困境?


更新:

似乎lazy="dynamic" is explicitly discouraged here,但没有其他建议。仍然允许分页并依靠大量馆藏的替代方法是什么?默认值不允许这样做(或者至少以我访问它的方式),并且文档似乎也无法阐明问题!在标题为What kind of loading to use?的小节中,似乎建议为大型馆藏使用的加载策略为lazy="subquery",但这不适用于paginate()count()

1 个答案:

答案 0 :(得分:2)

在SQLAlchemy如何处理形成动态加载关系查询的过程中,的确是an issue。虽然查询应该是

SELECT post.id AS post_id, post.head_id AS post_head_id 
FROM post, tags JOIN post_version ON tags.post_version_id = post_version.id 
WHERE ? = tags.tag_id AND post.head_id = post_version.id

最终以

SELECT post.id AS post_id, post.head_id AS post_head_id 
FROM post, tags, post_version
WHERE ? = tags.tag_id AND post.head_id = post_version.id

因此,尽管postpost_version之间存在内部联接(采用SQL-92之前的样式),但tagspost_version之间的内部联接却丢失了因此在tags和其余部分之间有一个CROSS JOIN。结果是该查询将加载所有当前帖子版本,而与标签无关。 ,因为每个帖子都与tags中的每一行连接在一起。这也解释了t.posts.count()的乘法。

解决方案是等待fix,同时使用其他一些关系加载策略。