ORM 分页期间的 2 个查询

2 queries during pagination in ORM

我已经开始深入研究 ORM,突然一个问题出现在我的脑海中: 当我们使用它们的分页功能时,像 Hibernate 和 SqlAlchemy 这样的 ORM 会触发 2 个查询吗?

flask-sqlalchemy 中的分页对象提供 obj.totalobj.has_nextobj.page

等属性

SQLAlchemy 的工作方式与您预期的一样,有两个查询——实际上没有其他方法可以做到这一点。

例如,使用一个简单的 Post 模型,您会得到两个查询(我在配置中将 SQLALCHEMY_ECHO 设置为 True 以获得这些语句的回显)

>>> p = Post.query.paginate(per_page=20, page=5)
2021-05-05 14:51:28,647 INFO sqlalchemy.engine.Engine SELECT post.id AS post_id, post.user_id AS post_user_id, post.title AS post_title, post.content AS post_content, post.dttm AS post_dttm 
FROM post
 LIMIT ? OFFSET ?
2021-05-05 14:51:28,647 INFO sqlalchemy.engine.Engine [generated in 0.00026s] (20, 80)
2021-05-05 14:51:28,652 INFO sqlalchemy.engine.Engine SELECT count(*) AS count_1 
FROM (SELECT post.id AS post_id, post.user_id AS post_user_id, post.title AS post_title, post.content AS post_content, post.dttm AS post_dttm 
FROM post) AS anon_1
2021-05-05 14:51:28,652 INFO sqlalchemy.engine.Engine [generated in 0.00015s] ()

如您所见——偏移量实际上首先触发,它并没有在逻辑上将您限制在语句级别的实际返回行(您将不会返回任何内容)。

这样做有效率吗?好吧——它是解决问题的最有效方法。我相信最近有对 Flask-SQLAlchemy 的提交,所以将在即将发布的版本中,您可以选择禁用 count(*) 第二个查询,但显然您不知道您有多少页,如果这是您的用户界面所需要的东西,那么它就是让步。

您的数据库将尽其所能提高效率,如果是普通查询,它可能会缓存结果,也许它会注意到它只访问索引列并跳过完整的 table 扫描。例如,如果我用 SQLite 重新运行相同的命令,我会得到:

>>> p = Post.query.paginate(per_page=20, page=5)
2021-05-05 15:02:21,403 INFO sqlalchemy.engine.Engine SELECT post.id AS post_id, post.user_id AS post_user_id, post.title AS post_title, post.content AS post_content, post.dttm AS post_dttm 
FROM post
 LIMIT ? OFFSET ?
2021-05-05 15:02:21,403 INFO sqlalchemy.engine.Engine [cached since 61.8s ago] (20, 80)
2021-05-05 15:02:21,404 INFO sqlalchemy.engine.Engine SELECT count(*) AS count_1 
FROM (SELECT post.id AS post_id, post.user_id AS post_user_id, post.title AS post_title, post.content AS post_content, post.dttm AS post_dttm 
FROM post) AS anon_1
2021-05-05 15:02:21,404 INFO sqlalchemy.engine.Engine [cached since 61.8s ago] ()

请注意它是如何缓存先前的结果并因此 returns 立即缓存的。因此,虽然这不是一个令人满意的答案,但它的效率已达到一般水平。

有时不必知道具体有多少项,但只要知道还有更多元素就足够了。 Spring 数据调用此 Slice 而不是 Page。本质上,如果页面大小为 N,它将获取 N + 1 行,以便能够告诉您还有更多元素要获取。这最适合键集分页。不过,您可以将计数查询作为 select 项内嵌到主查询中,这是 Blaze-Persistence Pagination(在 JPA/Hibernate 之上工作)默认执行的操作。所有现代数据库都应该能够弄清楚计数查询是不相关的,因此只执行一次而不是每一行。

ORM 通常不做的一项优化是对计数查询进行连接修剪。 Blaze-Persistence 将愉快地修剪所有不必要的连接和其他子句,并因此大大提高性能。另一种改进计数的方法是避免对所有元素进行计数,而是只计数到某个界限。通常,最终用户不关心您找到了 101 个结果还是 1000 个结果,那么为什么要全部计算呢? Blaze-Persistence 可以轻松生成这样的有界计数查询:https://persistence.blazebit.com/documentation/1.6/core/manual/en_US/#bounded-counting