分页
Prisma Client 支持偏移分页和基于游标的分页。
偏移分页
偏移分页使用 skip
和 take
来跳过一定数量的结果并选择一个有限的范围。以下查询跳过前3条 Post
记录并返回第4至第7条记录
const results = await prisma.post.findMany({
skip: 3,
take: 4,
})
要实现结果分页,只需 skip
页面数乘以每页显示的结果数。
✔ 偏移分页的优点
- 您可以立即跳转到任何页面。例如,您可以
skip
200条记录并take
10条,这模拟了直接跳转到结果集的第21页(底层SQL使用OFFSET
)。这在基于游标的分页中是不可能实现的。 - 您可以以任何排序顺序对相同的结果集进行分页。例如,您可以跳转到按名字排序的
User
记录列表的第21页。这在基于游标的分页中是不可能实现的,因为它要求按唯一、顺序的列进行排序。
✘ 偏移分页的缺点
- 偏移分页在数据库层面**无法扩展**。例如,如果您跳过200,000条记录并取出前10条,数据库仍然需要遍历前200,000条记录才能返回您请求的10条——这会负面影响性能。
偏移分页的用例
- 对小型结果集进行浅层分页。例如,一个允许您按作者过滤
Post
记录并对结果进行分页的博客界面。
示例:过滤和偏移分页
以下查询返回 email
字段包含 prisma.io
的所有记录。该查询跳过前40条记录并返回第41至第50条记录。
const results = await prisma.post.findMany({
skip: 40,
take: 10,
where: {
email: {
contains: 'prisma.io',
},
},
})
示例:排序和偏移分页
以下查询返回 email
字段包含 Prisma
的所有记录,并按 title
字段排序结果。该查询跳过前200条记录并返回第201至第220条记录。
const results = await prisma.post.findMany({
skip: 200,
take: 20,
where: {
email: {
contains: 'Prisma',
},
},
orderBy: {
title: 'desc',
},
})
基于游标的分页
基于游标的分页使用 cursor
和 take
来返回给定**游标**之前或之后有限数量的结果。游标会在结果集中标记您的位置,并且必须是一个唯一、顺序的列——例如 ID 或时间戳。
以下示例返回前4条包含单词 "Prisma"
的 Post
记录,并将最后一条记录的 ID 保存为 myCursor
**注意**:由于这是第一次查询,没有游标可传入。
const firstQueryResults = await prisma.post.findMany({
take: 4,
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})
// Bookmark your location in the result set - in this
// case, the ID of the last post in the list of 4.
const lastPostInResults = firstQueryResults[3] // Remember: zero-based index! :)
const myCursor = lastPostInResults.id // Example: 29
下图显示了前4个结果的 ID——即第1页。下一次查询的游标是 **29**
第二次查询返回包含单词 "Prisma"
并在**提供的游标之后**的前4条 Post
记录(换句话说——ID 大于 **29** 的记录)
const secondQueryResults = await prisma.post.findMany({
take: 4,
skip: 1, // Skip the cursor
cursor: {
id: myCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})
const lastPostInResults = secondQueryResults[3] // Remember: zero-based index! :)
const myCursor = lastPostInResults.id // Example: 52
下图显示了 ID 为 **29** 的记录**之后**的前4条 Post
记录。在此示例中,新游标是 **52**
常见问题
我总是需要 skip: 1
吗?
如果您不 skip: 1
,您的结果集将包含您之前的游标。第一次查询返回四个结果,游标是 **29**
不使用 skip: 1
,第二次查询返回游标之后(并*包含*)的4个结果
如果您 skip: 1
,则不包含游标
您可以根据所需的分页行为选择是否使用 skip: 1
。
我可以猜测游标的值吗?
如果您猜测下一个游标的值,您将在结果集中分页到未知位置。尽管 ID 是顺序的,但您无法预测增量速率(2
、20
、32
比 1
、2
、3
更常见,尤其是在过滤后的结果集中)。
基于游标的分页是否使用底层数据库中游标的概念?
不,游标分页不使用底层数据库中的游标(例如 PostgreSQL)。
如果游标值不存在会发生什么?
使用不存在的游标会返回 null
。Prisma Client 不会尝试定位相邻值。
✔ 基于游标的分页的优点
- 基于游标的分页**可扩展**。底层 SQL 不使用
OFFSET
,而是查询所有 ID 大于cursor
值的Post
记录。
✘ 基于游标的分页的缺点
- 您必须按游标进行排序,游标必须是唯一、顺序的列。
- 您无法仅使用游标跳转到特定页面。例如,如果不首先请求第1至第399页,您无法准确预测哪个游标代表第400页(每页20条)的开头。
基于游标的分页的用例
- 无限滚动——例如,按日期/时间降序排序博客文章,并一次请求10篇博客文章。
- 分批遍历整个结果集——例如,作为长时间运行的数据导出的一部分。
示例:过滤和基于游标的分页
const secondQuery = await prisma.post.findMany({
take: 4,
cursor: {
id: myCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})
排序和基于游标的分页
基于游标的分页要求您按顺序、唯一的列进行排序,例如 ID 或时间戳。此值(称为游标)会在结果集中标记您的位置,并允许您请求下一组数据。
示例:使用基于游标的分页向后翻页
要向后翻页,请将 take
设置为负值。以下查询返回4条 Post
记录,其 id
小于200,不包括游标
const myOldCursor = 200
const firstQueryResults = await prisma.post.findMany({
take: -4,
skip: 1,
cursor: {
id: myOldCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})