分页
Prisma Client 同时支持基于偏移量的分页和基于游标的分页。
基于偏移量的分页
基于偏移量的分页使用 skip 和 take 来跳过一定数量的结果并选择一个有限的范围。以下查询跳过前 3 条 Post 记录并返回第 4 - 7 条记录。
const results = await prisma.post.findMany({
skip: 3,
take: 4,
})
要实现结果分页,只需将 skip 的数量设置为页数乘以每页显示的结果数量即可。
✔ 基于偏移量分页的优点
- 您可以立即跳转到任何页面。例如,您可以
skip200 条记录并take10 条,这模拟了直接跳转到结果集的第 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 或时间戳。
以下示例返回包含单词 "Prisma" 的前 4 条 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 个结果(或第 1 页)的 ID。下一次查询的游标是 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,
skip: 1,
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',
},
})