分页与游标
数据墙DBW 使用游标分页(Cursor-based Pagination)来实现数据的分批返回。与传统的页码分页不同,游标分页在数据变动时也能保证翻页的一致性。REST 使用 $first 和 $after,GraphQL 使用 first 和 after。
分页参数
| 参数 | REST | GraphQL | 说明 |
|---|---|---|---|
| 页大小 | $first | first | 每页返回的记录数 |
| 翻页游标 | $after | after | 上一页返回的游标令牌 |
默认分页行为
默认每页最多返回 100 条记录。可通过 runtime.pagination 修改:
json
{
"runtime": {
"pagination": {
"default-page-size": 50,
"max-page-size": 1000
}
}
}未传 $first/first 时使用默认值。传 -1 则返回配置的 max-page-size。
REST 分页
基本用法
bash
# 每页 5 条
curl "http://localhost:5000/api/Book?\$first=5"响应中带 nextLink 时表示还有更多数据:
json
{
"value": [
{ "id": 1, "title": "第一本" },
{ "id": 2, "title": "第二本" },
{ "id": 3, "title": "第三本" },
{ "id": 4, "title": "第四本" },
{ "id": 5, "title": "第五本" }
],
"nextLink": "http://localhost:5000/api/Book?$first=5&$after=WyJpZCI6NV0="
}翻页
从 nextLink 中提取 $after 值,作为下一页请求的参数:
bash
curl "http://localhost:5000/api/Book?\$first=5&\$after=eyJpZCI6NX0="$after 是一个 Base64 编码的游标令牌,客户端不需要解析其内容——直接用 nextLink 的值即可。
遍历全部数据(JavaScript)
javascript
async function fetchAllPages(baseUrl) {
let url = `${baseUrl}/api/Book?$first=20`;
const allItems = [];
while (url) {
const res = await fetch(url);
const data = await res.json();
allItems.push(...data.value);
url = data.nextLink || null;
}
return allItems;
}遍历全部数据(Python)
python
import requests
def fetch_all(base_url):
url = f"{base_url}/api/Book?$first=20"
items = []
while url:
res = requests.get(url)
data = res.json()
items.extend(data["value"])
url = data.get("nextLink")
return itemsGraphQL 分页
基本用法
graphql
{
books(first: 5) {
items { id title }
hasNextPage
endCursor
}
}响应:
json
{
"data": {
"books": {
"items": [
{ "id": 1, "title": "第一本" },
{ "id": 2, "title": "第二本" }
],
"hasNextPage": true,
"endCursor": "eyJpZCI6NX0="
}
}
}翻页
将上一页的 endCursor 值传给 after:
graphql
{
books(first: 5, after: "eyJpZCI6NX0=") {
items { id title }
hasNextPage
endCursor
}
}当 hasNextPage 为 false 时已到最后一页。
遍历全部数据(JavaScript)
javascript
async function fetchAllPages(gqlUrl) {
const allItems = [];
let cursor = null;
let hasNext = true;
while (hasNext) {
const res = await fetch(gqlUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `query($after: String) {
books(first: 20, after: $after) {
items { id title }
hasNextPage
endCursor
}
}`,
variables: { after: cursor }
}),
});
const { data } = await res.json();
const page = data.books;
allItems.push(...page.items);
hasNext = page.hasNextPage;
cursor = page.endCursor;
}
return allItems;
}分页与筛选/排序结合
分页与筛选排序一起使用时,注意顺序:先筛选,再排序,最后分页。
REST
bash
curl "http://localhost:5000/api/Book?\$filter=year%20ge%202000&\$orderby=pages%20desc&\$first=10"GraphQL
graphql
{
books(
filter: { year: { gte: 2000 } }
orderBy: { pages: DESC }
first: 10
) {
items { id title year pages }
hasNextPage
endCursor
}
}游标分页与页码分页的区别
| 特性 | 游标分页(DAB) | 页码分页 |
|---|---|---|
| 翻页方式 | 上一页的游标令牌 | 页码数字(page=1, page=2) |
| 数据变动时 | 不会重复或遗漏 | 可能重复或遗漏 |
| 跳页 | 不支持 | 支持跳转到任意页 |
| 实现复杂度 | 低(跟随 nextLink) | 低 |
| 性能 | 稳定 | 大偏移量时性能下降 |
数据墙DBW 选择游标分页的原因:数据库结果集在查询期间相对稳定,游标比页码偏移更高效,且不会因为中间插入新数据导致翻页结果错乱。
配置建议
| 场景 | default-page-size | max-page-size |
|---|---|---|
| 移动端、列表页面 | 20~50 | 200 |
| 后台管理、数据导出 | 100~200 | 1000 |
| 内部批量处理 | 500 | 10000 |
较小的默认值减少单次请求的数据库负载和网络传输量。客户端需要更多数据时再翻页。
