我们很高兴向大家介绍 **FastPage**,这是 ActiveRecord 的新 gem,它将 MySQL 的 “延迟连接”(deferred join)优化应用于你的 offset/limit 查询。


示例:改进后的分页查询

以下是一个在 Rails 中的慢速分页查询:

Post.all.order(created_at: :desc).limit(25).offset(100)
# Post Load (1228.7ms)  SELECT `posts`.* FROM `posts` ORDER BY `posts`.`created_at` DESC LIMIT 25 OFFSET 100

我们为查询添加了 .fast_page,现在快了 **2.7 倍**:

Post.all.order(created_at: :desc).limit(25).offset(100).fast_page
# Post Pluck (456.9ms)  SELECT `posts`.`id` FROM `posts` ORDER BY `posts`.`created_at` DESC LIMIT 25 OFFSET 100
# Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1271528, 1271527, 1271526, 1271525, 1271524, 1271523, 1271522, 1271521, 1271520, 1271519, 1271518, 1271517, 1271516, 1271515, 1271514, 1271512, 1271513, 1271511, 1271510, 1271509, 1271508, 1271507, 1271506, 1271505, 1271504) ORDER BY `posts`.`created_at` DESC

性能评估

为了评估延迟连接的性能提升,我们选取了一个包含约 100 万条记录 的表,分别对比了标准 ActiveRecord 的 offset/limit 查询与使用 FastPage 的查询。
以下是我们使用的查询:

AuditLogEvent.page(num).per(100).where(owner: org).order(created_at: :desc)

其中 ownercreated_at 都有索引。
性能测试图表 对比 ActiveRecord 和 FastPage 的性能数据,随着页码递增,FastPage 显示出几乎线性的性能表现,仅占标准方法查询时间的一小部分。


工作原理

分页的最常见实现方式是使用 LIMITOFFSET
以下是一个分页示例,每页返回 50 条博客帖子:

  • 第一页:提取前 50 条记录。
  • 第二页:提取前 100 条记录,丢弃前 50 条。
  • 第三页:提取前 150 条记录,丢弃前 100 条。随着 OFFSET 的增加,数据库为每页提供服务的成本会逐渐上升。
-- Page 1
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50;
-- Page 2
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 50;
-- Page 3
SELECT * FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 100;

这种分页方式在记录较少的情况下效果不错,但当记录数量很大时,后续页面的查询成本会变得非常高昂。由于这个原因,许多应用不得不限制可查看的最大页数,或切换到基于游标的分页方式。


延迟连接的技术

《High Performance MySQL》推荐通过 延迟连接 (deferred join) 来提高大表的 LIMIT/OFFSET 分页效率。
以下是示例查询:

SELECT * FROM posts
INNER JOIN (SELECT id FROM posts ORDER BY created_at DESC LIMIT 50 OFFSET 10000)
AS lim USING(id);

注意,我们首先选择要显示的行的 id,然后再查询这些行的数据。这种技术之所以有效,是因为它允许服务器在不访问行的情况下仅扫描索引中的少量数据。
FastPage gem 使你能够轻松将此优化应用到任何使用 offset/limit 查询的 ActiveRecord::Relation 对象。


什么时候应该使用 fast_page?

fast_page 最适合用于包含 ORDER BY 的分页查询。随着页码增加,其效果会更显著。建议在测试你应用的实际数据时比较查询时间的改善程度。
由于 fast_page 运行两个查询而非一个,在早期页面查询时可能稍慢。当用户进入更深的页面时,性能优势会逐渐显现。你可以测试在第几页开始使用 fast_page 能够显著提升查询性能并在特定页码之后应用该优化:

posts = Post.all.page(params[:page]).per(25)
# 在第 5 页后使用 fast_page,提升查询性能
posts = posts.fast_page if params[:page] > 5


介绍 FastPage:为 Rails 应用提供更快的偏移分页插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://www.choupangxia.com/2025/09/07/fastpage-for-rails/