4. 分页参数设计
行情接口的分页参数看似只是“翻页”,实际上它同时决定了数据一致性、查询性能、限流成本和重试语义。设计不当会出现重复K线、漏数据、深分页拖垮数据库,甚至在重试时把同一页数据反复拉取。本节把常见分页参数分成三组:页码型、偏移型、游标/时间型,并给出边界处理与选型建议。
一、三组分页参数的语义差异
页码型以 page 与 page_size 为核心,语义是“第几页、每页多少条”。它对人友好,适合后台管理页面,但在行情场景中风险最大:当新数据不断写入头部时,第 2 页的内容会整体后移,导致重复或遗漏。偏移型使用 offset 与 limit,语义是“跳过多少条、取多少条”,本质与页码型相同,只是换了一种表达;offset 越大,数据库扫描成本越高。游标/时间型使用 cursor、since、until,语义是“从某个位置之后继续取”,天然抗数据插入,适合增量同步与高频轮询。
二、参数语义与边界处理清单
| 参数 | 语义 | 典型边界 | 处理建议 |
|---|---|---|---|
| page | 第几页,从1开始 | 0、负数、超大值 | 小于1时归一为1;超过总页数返回空数组而非报错 |
| page_size | 每页条数 | 0、负数、超过上限 | 默认20,最大200;超限截断并返回实际值 |
| offset | 跳过条数 | 负数、超过总量 | 负数归零;建议限制最大 offset,避免深分页 |
| limit | 返回条数 | 0、超大值 | 与 page_size 共用上限;0 可视为取默认值 |
| cursor | 上一页最后一条的位置标识 | 过期、伪造、格式错误 | 校验签名与有效期;无效时返回 400 并提示重新同步 |
| since | 起始时间(含) | 晚于 until、未来时间 | since > until 时交换或报错;未来时间返回空 |
| until | 结束时间(不含或含需明确) | 早于 since、格式错误 | 统一为左闭右开 [since, until),避免边界重复 |
三、游标分页的工作流程
游标分页的核心是“位置”而不是“页码”。服务端把排序键(如时间戳+唯一ID)编码成 cursor,客户端下次请求原样带回。这样即使期间有新数据插入,也不会影响已翻过的位置。
GET /v1/klines?symbol=BTCUSDT&interval=1m&limit=100
{
"data": [ ... ],
"next_cursor": "eyJ0IjoxNzEwMDAwMDAsImlkIjoiYWJjMTIzIn0=",
"has_more": true
}
# 下一页
GET /v1/klines?symbol=BTCUSDT&interval=1m&limit=100&cursor=eyJ0IjoxNzEwMDAwMDAsImlkIjoiYWJjMTIzIn0=
时间型参数 since/until 常用于补历史数据。建议统一采用左闭右开区间:[since, until)。这样连续请求 [T1, T2) 与 [T2, T3) 不会重复 T2 时刻的数据,也不会漏掉。若接口文档写的是闭区间,必须在文档和代码中显式说明,否则重试时极易产生重复K线。
四、案例:高频轮询中的分页选择
假设每 5 秒轮询一次最新成交,若使用 page=1&page_size=100,当 5 秒内新增 150 条时,第 1 页只能拿到最新 100 条,剩余 50 条被挤到第 2 页;如果客户端只请求第 1 页,就会漏数据。正确做法是记录上次最后一条的时间戳与ID,使用 since=last_ts&limit=500 持续拉取,直到返回条数小于 limit 为止。
last_ts = 0
while True:
resp = get("/trades", params={"since": last_ts, "limit": 500})
if not resp["data"]:
break
for item in resp["data"]:
process(item)
last_ts = resp["data"][-1]["ts"] + 1 # 左闭右开,避免重复
if len(resp["data"]) < 500:
break
这段代码体现了两个边界处理:一是 last_ts + 1 保证不重复;二是“返回条数小于 limit”作为终止条件,避免无限循环。若接口使用 cursor,则把 last_ts 换成 next_cursor,逻辑一致。
五、常见误区与复盘
- 误区一:page 与 cursor 混用。同一接口同时支持两种分页,客户端切换时容易把 page 当成 cursor 传入,导致 400 或数据错乱。建议在参数校验层明确互斥。
- 误区二:忽略 limit 上限。客户端传
limit=100000,服务端不截断,直接拖垮数据库。必须设置硬上限并返回实际生效值。 - 误区三:since/until 边界不明确。文档写“包含”,代码写“不包含”,重试时同一秒数据重复入库。统一左闭右开并写入契约测试。
- 误区四:cursor 无过期时间。长期有效的 cursor 可能指向已被清理的数据,导致查询报错。建议设置 TTL,并在失效时返回明确错误码。
- 误区五:深分页不设限。
offset=1000000会扫描大量无用行。行情场景应改用游标或时间范围,并对 offset 设置最大值。
六、参数选型速查
静态历史数据、后台报表:page/page_size 足够;增量同步、高频轮询:cursor 或 since/until;需要按时间窗口补数:since/until 最直观;需要严格不重不漏:cursor + 唯一排序键。无论选哪种,都要在接口层做参数归一化、上限截断、边界校验和错误码约定,并在文档中写明区间开闭与默认值。