还在使用 Jaeger/Sentry?Uptrace 是一个 开源 APM,用于 OpenTelemetry,您可以使用它来监控应用程序并设置警报以通过电子邮件、Slack、Telegram 等接收通知。

PostgreSQL/MySQL 的游标分页

游标分页是一种有用的技术,可以提高显示大量数据的 Web 应用程序的性能和可用性。

使用游标分页,服务器会将一页数据发送到客户端,以及一个游标,该游标标识页面中最后一项的位置。客户端可以使用此游标请求下一页数据,并将游标作为参数传递给服务器。

Cursor pagination

简介

通常,您可以使用 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 Overview

Uptrace 带有一个直观的查询构建器、丰富的仪表板、带有通知的警报规则以及大多数语言和框架的集成。

Uptrace 可以在一台服务器上处理数十亿个跨度和指标,并允许您以 10 倍更低的成本监控您的应用程序。

只需几分钟,您就可以通过访问 云演示在新窗口中打开(无需登录)或使用 Docker在新窗口中打开 在本地运行它。源代码可在 GitHub在新窗口中打开 上获得。