介绍 FastPage:为 Rails 应用提供更快的偏移分页
我们很高兴向大家介绍 **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)
其中 owner
和 created_at
都有索引。
性能测试图表 对比 ActiveRecord 和 FastPage 的性能数据,随着页码递增,FastPage 显示出几乎线性的性能表现,仅占标准方法查询时间的一小部分。
工作原理
分页的最常见实现方式是使用 LIMIT
和 OFFSET
。
以下是一个分页示例,每页返回 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
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接
本文链接:http://www.choupangxia.com/2025/09/07/fastpage-for-rails/