ORM 分页期间的 2 个查询
2 queries during pagination in ORM
我已经开始深入研究 ORM,突然一个问题出现在我的脑海中:
当我们使用它们的分页功能时,像 Hibernate 和 SqlAlchemy 这样的 ORM 会触发 2 个查询吗?
- 如果没有,那么尽管设置了 pageNo 和 pageSize,他们如何显示结果集总数?
- 如果是,效率高吗? 运行 针对 table 中所有记录的额外查询以获得实际计数,然后 运行 使用
LIMIT
和 OFFSET
[=23= 进行第二个查询]
flask-sqlalchemy
中的分页对象提供 obj.total
、obj.has_next
、obj.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
我已经开始深入研究 ORM,突然一个问题出现在我的脑海中: 当我们使用它们的分页功能时,像 Hibernate 和 SqlAlchemy 这样的 ORM 会触发 2 个查询吗?
- 如果没有,那么尽管设置了 pageNo 和 pageSize,他们如何显示结果集总数?
- 如果是,效率高吗? 运行 针对 table 中所有记录的额外查询以获得实际计数,然后 运行 使用
LIMIT
和OFFSET
[=23= 进行第二个查询]
flask-sqlalchemy
中的分页对象提供 obj.total
、obj.has_next
、obj.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