API 分页用 OFFSET 还是游标?翻到后面会慢多少?翻页时数据变了会怎样?db.paginate() 对非法参数怎么处理?这篇在真实的 20 万行数据上把耗时和一致性逐项量出来。
第 1 页(OFFSET 0) 0.67 ms 第 1000 页(OFFSET 19980) 1.54 ms 第 5000 页(OFFSET 99980) 5.14 ms 第 9999 页(OFFSET 199960) 9.71 ms
从 id> 0 开始 0.57 ms 从 id> 19980 开始 0.62 ms 从 id> 99980 开始 0.63 ms 从 id>199960 开始 0.60 ms EXPLAIN(OFFSET 199980): Limit (cost=7728.33..7728.42 rows=1 width=82) EXPLAIN(id > 199980): Limit (cost=0.42..4.56 rows=20 width=82) (EXPLAIN 估算成本:OFFSET 约 7728,keyset 约 4.6——OFFSET 要先数过前面的 199980 行) 顺便:select count(*) 取总数 7.3 ms(200000 行)
第 1 页 ids = [1, 2, 3] → 此时删掉 id=1 → OFFSET 的第 2 页 = [5, 6, 7] (漏掉了 4)| keyset 的下一页 = [4, 5, 6]
第一页返回的 next = eyJhZnRlciI6IDIwfQ== → 解码: {"after": 20}
用它取第二页的前 3 个 id: [21, 22, 23]
篡改的游标 '###' → 400
篡改的游标 'eyJhZnRlciI6IC' → 400
(游标只是编码不是加密——客户端能读也能伪造,服务端必须校验;不要把敏感值放进游标) /paginate → 200 {'first': 1, 'has_next': True, 'n': 20, 'page': 1, 'pages': 10000, 'per_page': 20, 'total': 200000}
/paginate?page=3&per_page=50 → 200 {'first': 101, 'has_next': True, 'n': 50, 'page': 3, 'pages': 4000, 'per_page': 50, 'total': 200000}
/paginate?per_page=1000 → 200 {'first': 1, 'has_next': True, 'n': 100, 'page': 1, 'pages': 2000, 'per_page': 100, 'total': 200000}
/paginate?page=0 → 404 <!doctype html>
<html lang=en>
<title>40
/paginate?page=99999 → 404 <!doctype html>
<html lang=en>
<title>40
/paginate?page=abc → 404 <!doctype html>
<html lang=en>
<title>40
/paginate?per_page=0 → 404 <!doctype html>
<html lang=en>
<title>40