PostgreSQL/MySQL 的游标分页
游标分页是一种有用的技术,可以提高显示大量数据的 Web 应用程序的性能和可用性。
使用游标分页,服务器会将一页数据发送到客户端,以及一个游标,该游标标识页面中最后一项的位置。客户端可以使用此游标请求下一页数据,并将游标作为参数传递给服务器。
简介
通常,您可以使用 LIMIT X OFFSET Y
对结果进行分页
SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 0; -- first page
SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 10; -- second page
SELECT * FROM entries ORDER BY id ASC LIMIT 10 OFFSET 20; -- third page
这种分页方法效果很好,但您可能会注意到,随着偏移量的增加,查询变得越来越慢。这是因为 OFFSET 100000
告诉数据库读取并丢弃 100,000 行,这使得大偏移量的性能不可接受。这里通常的解决方法是限制允许的偏移量范围,例如,您可以将允许的页面数限制为 1000。
但是,如果您无法限制页面数怎么办?例如,GitHub 必须允许用户查看存储库中的所有提交,无论存储库有多大。答案是游标分页。
游标分页
基于游标的分页通过向客户端返回指向页面上最后一项的指针(游标)来工作。要获取下一页,客户端将游标传递给服务器,服务器返回给定游标后的结果。这种方法的主要限制是客户端无法跳转到特定页面,也不知道总页面数。
提示
基于游标的分页提供的用户体验远不如经典分页。
仅在必须时使用它。
由于游标必须明确地标识行,因此您只能对主键或具有唯一约束的列使用基于游标的分页。这也确保查询使用索引,并且可以快速跳过已分页的行。
游标分页与偏移分页
与传统的基于页面的分页相比,游标分页具有以下几个优点
性能。游标分页减少了需要从数据库中检索的数据量,从而缩短了页面加载时间并减少了服务器负载。
稳定性。与基于页面的分页相比,游标分页提供更稳定和可预测的分页,基于页面的分页在导航页面时,如果数据添加或删除,可能会导致分页不一致。
所有这些都以降低灵活性为代价。游标分页不允许用户跳转到数据集中的任何位置,而无需遍历所有先前的页面。
示例
让我们使用主键作为指针对以下模型进行分页
type Entry struct {
ID int64
Text string
}
我们的辅助 Cursor
结构可能如下所示
type Cursor struct {
Start int64 // pointer to the first item for the previous page
End int64 // pointer to the last item for the next page
}
要检索下一页,我们需要从指向最后一项的游标继续
func selectNextPage(ctx context.Context, db *bun.DB, cursor int64) ([]Entry, Cursor, error) {
var entries []Entry
if err := db.NewSelect().
Model(&entries).
Where("id > ?", cursor).
OrderExpr("id ASC").
Limit(10).
Scan(ctx); err != nil {
return nil, Cursor{}, err
}
return entries, NewCursor(entries), nil
}
要检索上一页,我们需要从指向第一项的游标开始向后迭代
func selectPrevPage(ctx context.Context, db *bun.DB, cursor int64) ([]Entry, Cursor, error) {
var entries []Entry
if err := db.NewSelect().
Model(&entries).
Where("id < ?", cursor).
OrderExpr("id DESC").
Limit(10).
Scan(ctx); err != nil {
return nil, Cursor{}, err
}
return entries, NewCursor(entries), nil
}
我们可以像这样使用这些方法
page1, cursor, err := selectNextPage(ctx, db, 0)
if err != nil {
panic(err)
}
page2, cursor, err := selectNextPage(ctx, db, cursor.End)
if err != nil {
panic(err)
}
prevPage, _, err := selectPrevPage(ctx, db, cursor.Start)
if err != nil {
panic(err)
}
有关详细信息,请参阅 示例。
监控性能
要 监控 Bun 性能,您可以使用 Bun 附带的 OpenTelemetry 检测。
通过使用 OpenTelemetry,开发人员可以深入了解其应用程序的性能以及不同组件之间的交互,从而更轻松地排查问题、优化性能并提高分布式系统的整体可靠性。
Uptrace 是一个 OpenTelemetry 后端,支持分布式跟踪、指标和日志。您可以使用它来监控应用程序并排查问题。
Uptrace 带有一个直观的查询构建器、丰富的仪表板、带有通知的警报规则以及大多数语言和框架的集成。
Uptrace 可以在一台服务器上处理数十亿个跨度和指标,并允许您以 10 倍更低的成本监控您的应用程序。