首页 > 解决方案 > 懒惰=“动态”有什么问题?有哪些替代方案?

问题描述

这个问题已经完全重写了 10/17/18

为了有一个“编辑版本控制系统”(类似于 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可变标记和通过编辑可变版本组成;也就是说:我生成了 136Post的。我有 15Tag的。最初的 136Post都与 2 一致地标记Tag。我随后用不同的标签对 's 进行了可变标记(使用我的编辑系统;因此已编辑的 'sPost有多个' s)。PostVersionPost

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

关系 1posts

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>

关系 2posts2

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带有 0Post的标签)和相同的数据(即,结果posts2与 for 相同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" 明确不鼓励,但没有建议替代方案。仍然允许分页并依靠大型集合的替代方法是什么?默认情况下不允许这样做(或者至少在我访问它的方式上)并且文档似乎没有澄清问题!在标题为使用哪种加载方式的部分中?它似乎为大型集合推荐的加载策略是lazy="subquery",但这不允许paginate()and count()

标签: pythonflasksqlalchemyflask-sqlalchemy

解决方案


这确实是 SQLAlchemy 如何处理形成动态加载关系的查询的一个问题。虽然查询应该是

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

post因此,虽然and之间存在内部连接post_version(在SQL-92之前的样式中),但缺少 and 之间的内部连接,因此tags在与其余部分之间存在CROSS JOIN 。结果是查询加载了所有当前的帖子版本,而不管 tag(s)是什么,因为每个帖子都与. 这也解释了 的乘法。post_versiontagstagst.posts.count()

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


推荐阅读