Skip to content

fix(spec,plugins): a paged read with no orderBy is a partition too — the shape every list view actually sends (#4363) - #4378

Merged
os-zhuang merged 5 commits into
mainfrom
claude/pagination-tie-breaker-missing-p98qf0
Jul 31, 2026
Merged

fix(spec,plugins): a paged read with no orderBy is a partition too — the shape every list view actually sends (#4363)#4378
os-zhuang merged 5 commits into
mainfrom
claude/pagination-tie-breaker-missing-p98qf0

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #4363#4367 收口了「有 orderBy 的分页」,本 PR 收口它按 Prime Directive #10 拆出去的另一半。

#4367 已在本 PR 开出后合入 main,已 merge 进来(十处冲突全是同一段内容从两条路径到达 —— 逐文件比对确认 origin/main 与我 fork 出来的那个 branch head 逐字节相同,所以取本侧不丢任何东西)。合并后重装、全量重建、重跑,diff 现在只有本 PR 自己的改动。

决策:走方向 1,并且量过了

issue 列了三个方向、倾向 1(「orderBy 为空且 limit/offset 存在时按主键排序」),但要求「在真实数据量上先量一次计划变化再定」。量了 —— Postgres 16、2,000,000 行、390 MB、VACUUM FULL ANALYZE 之后、热缓存、best of 3:

形态 before after 计划
列表第 1 页 limit 50 0.038 ms 0.089 ms seq scan → ticket_pkey
列表第 201 页 limit 50 offset 10000 0.764 ms 10.3 ms seq scan → ticket_pkey
深翻页 limit 50 offset 1000000 70.6 ms 1089 ms seq scan → ticket_pkey
过滤(命中 25%)limit 50 0.056 ms 0.249 ms seq scan → ticket_pkey
过滤(命中 400/2M)limit 50 0.440 ms 1.885 ms ticket_owner_idx 不变,加 top-N sort
过滤(命中 400/2M)limit 5 0.088 ms 2.024 ms ticket_owner_idx 不变,加 top-N sort
findOne 形态 limit 1 0.077 ms 7.84 ms ticket_owner_idxticket_pkey(放弃谓词自己的索引)

结论分两条:

分页读取本身可以接受。 谓词有索引时计划不变,只多一个 top-N sort(毫秒级);无谓词时从顺序扫描换成主键索引扫描,代价随 offset 线性上升。深翻页那一格(1.09 s)确实难看,但它 before 就已经是 70 ms 的病态形态,文档本来就建议改用 keyset —— 而且它 before 之所以快,正是因为它返回的是错的结果。这是把「快而不确定」换成「慢而确定」,方向没得选。

limit 1 是另一回事,所以我把 findOne 排除在外了。 这一条 issue 没预见到:engine.findOne() 按契约写死 limit: 1,而 SqlDriver.findOne() 是转调 find() 实现的 —— 于是「分页读取」这条规则会顺手盖住全系统最热的那条读路径。ORDER BY id LIMIT 1 恰好是让 planner 判断「反正很快就能停」从而放弃谓词索引、改走主键加过滤的经典形态:上表最后一行,约 100×。

而且这里什么都换不到findOne 承诺的是「一条匹配的记录」,不是「序列中的某个位置」,没有第二页可以跟它不一致。所以 findfindOne 从共享的 findRows() 分叉,singleRowLookup 只影响这一件事。顺带的好处是这让两个 driver 重新对齐 —— MongoDBDriver.findOnecollection.findOne,从来就没排过序。

契约里也写下了这条边界(IDataDriver.findOne 的 JSDoc),免得下一个人把它当成漏掉的。

没走 2 和 3 的理由。 方向 2(全量补默认排序)上表已经回答了:谓词有索引的读取要多付一个 top-N sort,无谓词的要从顺序扫描换成主键索引扫描 —— 而不分页的读取把整个匹配集交给调用方,没有任何一页可以是错的,这笔钱买不到东西。方向 3(在 protocol 边界对「top/skip 存在而 sort 缺失」告警或拒绝)把决定权交回调用方,姿态上和 #4226 同源,但它要求每一个现存调用方都改:而这些调用方今天并没有做错事 —— 「不关心顺序」是一个合法的意图,只是它不该顺带意味着「可以少给我一行」。真正错的是我们把「不关心顺序」实现成了「不保证是个划分」。所以修在 driver,理由和 tie-breaker 一样:哪一列唯一、表上到底有没有,只有 driver 知道。

改了什么

契约(IDataDriver.find —— 义务从「orderBy 非空 + 分页」放宽到「只要分页」,两种破坏形态并列写出:不能标识行的排序键,和根本没有排序键(后者是前者的退化情形 —— 空排序键上每一行都跟每一行并列)。原来那段「Deliberately NOT covered」换成真正剩下的那个 carve-out:不分页的无序读取。

SqlDriver —— ORDER BY 的构造收进 orderKeysFor(),三种形态一张表:非空 orderBy → 调用方的键 + id;空 orderBy + 分页 → 只有 id;空 orderBy + 不分页 → 什么都不加limit 单独出现也算分页:列表第一页常常就是 limit=50 不带 offset,只给带 offset 的页排序等于让第 1 页跟后面的页切自不同的排列 —— bug 原封不动,外面套一层像修好了的样子。#3821 的恢复梯子也跟着改问「这条语句到底有没有 ORDER BY」,而不再问「调用方给没给」—— 自本 PR 起这是两个问题。

MongoDBDriver —— buildSortSpec() 改读整个 query 而非只读 orderBy。无 sort 时文档按 natural order 返回,而 natural order 描述的是文档当前在 extent 里的位置,文档一动它就变。syncCollectionSchema 给每个它建的集合都建了唯一索引 idx_id_unique,所以以 id 收尾的 sort 走索引,不会撞 100 MB 的内存排序上限。

driver-memory 又一次不用改 —— 它切的是自己的存储数组,两次读之间没有写就是同一个序列。契约要的是「划分」,不是「按 id 排」,这个 driver 从另一个方向满足它。给它补了用例,理由跟 #4367 给它补用例一样:这个保证是隐式的,一次看起来像提速的重构就会丢掉,而且不会让这个包里任何别的测试变红。

联邦表现在会出声。 SqlDriver 保留 #4367 的限制(只对自己建的表加序),但这里的代价比 sorted 那半大:sorted 的读取至少还留着调用方自己的 ORDER BY,只是其中的并列会重排;unsorted 的读取则一无所有。行为不变,但每个对象报一次 warn —— 契约现在把确定性写成 MUST,一个静悄悄不成立的 MUST 就是这条规则本来要消灭的那种失败。

测试

合并 main(含刚落地的 #4367#4373)之后 pnpm install --frozen-lockfile + 全量 pnpm build + 清 .objectstack/,再全部重跑:

suite 结果
全仓 pnpm test 132 / 132 tasks successful(含 dogfood 74 files、cliexample-showcase)—— 合并前跑过一次,合并后再跑一次
driver-sql 全量 615 passed / 38 skipped
driver-memory 全量 281 passed
driver-sqlite-wasm 全量 191 passed(继承 SqlDriver,同一条代码路径)
driver-mongodb 全量 84 passed / 93 skipped(见下:环境限制)
@objectstack/spec 全量 7161 passed
objectql / rest / runtime 1450 / 539 / 974 passed
@objectstack/spec check:generated 8/8 up to date(api-surface.json 重新生成,恰好 +2 行 = 两个新导出)
check:liveness / check:exported-any / check:type-check-coverage / check:doc-authoring / check:role-word / check:nul-bytes 全绿
typecheck(spec、driver-mongodb)+ 改动文件 eslint clean(driver-sql/driver-memory 在覆盖率台账里无 typecheck script)

两处 mutation check(不是「跑绿了」而是「拆了会红」):把 SqlDriver 里的 paged 钉成 false → 5 个用例红;把 MongoDBDriver 的同一处钉成 false → 1 个红。

顺手修了 #4367 两个测试上的洞,因为我正好要往它们上面加断言:

  1. SQL 那半的 clause 断言原本证明不了它想证明的事。 InspectableSqlDriver.orderByClauseFor() 是在测试里重新实现了一遍 tie-breaker 逻辑再编译 SQL —— 也就是说 find() 哪天不再调用它,这个断言照样绿,而那个文件的头注释写的恰恰是「clause 断言是删掉功能时会红的那一半」。改成从 knex 的 query 事件上读真实 find() 发出去的那条语句
  2. Mongo 那半的 sort-spec 断言挂在一个它不需要的依赖上。 那几条是纯函数断言,却和属性用例一起被 skipIf(!sharedMongod) 罩着。于是在拉不到 mongod 二进制的环境里,整个文件 —— 包括唯一能抓住功能被删的那半 —— 全体静默,而且报绿。挪到 skipIf 外面,用一个只构造、不 connect 的 driver 跑(构造函数不开 socket)。

环境限制(需要 CI 覆盖的部分)

MongoDB 的属性用例在本环境跑不了。 mongodb-memory-server 要下载的 fastdl.mongodb.org 被网络策略挡掉(agent proxy 403,__agentproxy/statusrecentRelayFailures 里有记录),所以需要真实 mongod 的用例是 skipped。上面第 2 条改动的直接结果是:即便如此,4 条 sort-spec 断言仍然真实执行并通过 —— 换成改之前,这个文件在这里会是 17 skipped、0 assertion,看起来跟「全过」一模一样。真实 mongod 上的那一半以 CI 为准。

Postgres 的数字是本机真跑的(本地 initdb 起的 16.13 实例,2M 行、390 MB,用完已拆);SQLite 由 driver-sql 套件覆盖。MySQL 没量。

关联

🤖 Generated with Claude Code

os-zhuang and others added 3 commits July 31, 2026 17:07
… five queries that share a WHERE clause (objectui#3106)

`ORDER BY status LIMIT 50 OFFSET 50` names a sort key that does not identify a
row, and no backend promises that rows with equal keys keep the same relative
arrangement between two queries. MongoDB documents this outright: `sort` +
`skip`/`limit` on a non-unique key may return the same document more than once.
Page 2 then repeats a row page 1 already showed and skips one nobody ever sees —
with every page full, every row real, and the two halves of the symptom several
screens apart.

SqlDriver and MongoDBDriver append a unique tie-breaker to any non-empty
`orderBy`, in the last requested key's direction: determinism holds either way,
but a same-direction suffix is the one an index can still walk in a single pass.

SqlDriver applies it only to objects it created itself (`initObjects` records
those in `managedObjectFields`). A federated table (ADR-0015) may carry no `id`
column, and guessing there would be worse than doing nothing — the resulting
unknown-column error is answered by #3821's recovery ladder retrying with NO
ORDER BY at all, trading a reshuffle among ties for the loss of the caller's
whole sort.

driver-memory needed no change: `Array#sort` is stable and the backing table's
order does not move between reads. It gets a suite anyway, because that
guarantee is implicit and is exactly what a refactor that looks like a speed-up
(a hand-rolled sort, or sorting the array in place) would silently remove.

The obligation is normative on `IDataDriver.find` and the cases are shared
(`PAGINATION_CASES` in `@objectstack/spec/data`), so a future driver is held to
it by a gate rather than by remembering. A paged read with NO `orderBy` is
deliberately out of scope and filed as #4363.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…4363)

objectui#3106's server half closed the SORTED paged read: a non-empty
`orderBy` carries a unique tie-breaker, so `ORDER BY status LIMIT 50
OFFSET 50` can no longer serve one row twice while never serving
another. It stopped there deliberately. This closes the half it left,
which is the more common one.

A list view whose metadata configures no `sort`, on which nobody has
clicked a column header, sends no `$orderby` at all. `SqlDriver` and
`MongoDBDriver` then emitted a bare LIMIT/OFFSET, and neither backend
promises anything about the order that slices: SQL leaves the row order
of an unordered read to the plan, and MongoDB's natural order moves when
a document does. Every row ties with every other on an empty sort key,
so it is the same defect at full strength rather than a different one.

Both drivers now order a paged read by their unique key column when the
caller supplied no sort keys -- the same `id` the tie-breaker was already
appending, standing alone. `driver-memory` again needed no change: it
slices its backing array, and two reads with no write between them see
the identical sequence. The contract asks for a partition, not id order.

Unpaged reads are untouched, deliberately: the rule keys off
`limit`/`offset`, not off `orderBy` being absent. `limit` alone does
count -- page one of a walk is routinely `limit=50` with no offset, and
ordering only the later pages would leave the defect fully intact.

`findOne` is outside all of this, and the contract now says so. Engines
reach a driver with `limit: 1`, shaped exactly like page one of a walk,
but it promises A matching record rather than a position in a sequence.
Reading it as a page would put `ORDER BY id LIMIT 1` on the hottest read
in the system, the classic shape for a planner to abandon the predicate's
own index: measured on Postgres 16 over 2M rows, `WHERE owner_id = ?
LIMIT 1` went 0.08ms -> 7.8ms and swapped the owner_id index for the
primary key. `MongoDBDriver.findOne` has never sorted, so this also puts
the two drivers back in step.

Federated tables (ADR-0015) keep the existing carve-out -- no ordering
column can be guessed there -- but now say so once per object, because
the contract states determinism as a MUST and a MUST that quietly does
not hold is the failure the rule was written against.

The obligation is normative on `IDataDriver.find` and the cases are
shared (`PAGINATION_UNORDERED_CASES` beside `PAGINATION_CASES`), so a
future driver is held to both halves by a gate rather than by memory.

Also repairs two holes in the tie-breaker suites this builds on:
`InspectableSqlDriver` reimplemented the ORDER BY instead of observing
the one `find()` emits (green on the day `find()` stops calling it), and
MongoDB's pure sort-spec assertions sat behind a `skipIf` on a server
they do not need -- so the only check that catches a deleted feature went
silent, and reported green, wherever the mongod binary cannot be fetched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016niiseyRn43L9M4JdAwuqD
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 31, 2026 10:34am

Request Review

…-breaker-missing-p98qf0

# Conflicts:
#	.changeset/paged-read-determinism.md
#	packages/plugins/driver-memory/src/memory-pagination-conformance.test.ts
#	packages/plugins/driver-mongodb/src/mongodb-driver.ts
#	packages/plugins/driver-mongodb/src/mongodb-pagination-conformance.test.ts
#	packages/plugins/driver-sql/src/sql-driver-pagination-conformance.test.ts
#	packages/plugins/driver-sql/src/sql-driver.ts
#	packages/spec/api-surface.json
#	packages/spec/src/contracts/data-driver.ts
#	packages/spec/src/data/index.ts
#	packages/spec/src/data/pagination-conformance.ts
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:data tests tooling labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 10:34
@os-zhuang
os-zhuang merged commit 8b50cb3 into main Jul 31, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/pagination-tie-breaker-missing-p98qf0 branch July 31, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

分页读取在没有 orderBy 时同样不确定:tie-breaker 只覆盖了「排了序的翻页」

2 participants