介绍
同事已经优化了商店的两个首页查询。取货队列最早订单优先,顾客历史最新订单优先。现在调用者需要继续读取前七笔之后的订单。几笔真实订单共享一个时间戳,因此只有时间戳的游标可能丢失或重复记录。
本独立单元提供自己的 MySQL 快照、可用的首页 queue.sql 和 history.sql、已应用索引及 indexes.sql。无需上一 VM 的文件。你将编辑两个保存的查询,让每笔原始订单在完整升序和降序逐页遍历中保留,同时保持高效索引访问。
前置知识为第一单元的 MySQL 参数、半开边界、双列排序和诊断计划。本单元讲解可为空的游标、完整比较条件和深页检查。使用 Terminal 和 /home/labex/project/order-desk。只修改 queue.sql 和 history.sql;保留提供的索引及索引源、所有订单事实、列、主键和可执行工具。
每页仍最多包含七笔实际订单,最多使用 64 次原生诊断行请求。准备好的连接在此受控比较中保持索引条件下推关闭;保留设置,避免优化器提示。请求使用固定数据快照,不宣称并发更新快照、物理 I/O 次数或生产延迟保证。运行时标签和输出保持英文。
将两个键带入取货队列下一页
本步骤中,你将推进最早订单优先的队列,而不丢失相同时间戳的剩余订单。
从 Terminal 的普通 shell 开始。提供的 db 和 desk 与第一单元练习过的接口相同。路径或文件名错误时,用 ls 检查实际名称并重试,不要改变原始行。这里 cat 打印保存的查询文本,随后的 page 请求读取其实际输出;没有参考查询修复缺失行。
cd /home/labex/project/order-desk
cat queue.sql
./desk page queue
保存的查询已有有效的店铺、状态、时间条件和 (placed_at,order_id) 排序,但没有游标条件。首页正确返回 900001 到 900007。工作台的 cursor_for_next_request 包含实际最后一行的时间和 ID,标识下一请求可以从何处开始,并不保证还有非空下一页。
--after 接收两个参数:实际最后时间戳和实际最后订单 ID。引号将时间戳中的空格保留在一个 shell 参数中。用刚观察的游标请求下一页:
./desk page queue --after "2025-05-10 09:15:00.000000" 900007
未修改的起始查询忽略游标,因此又返回前七个 ID。所有命令执行成功,但下一页错误。不要改变源时间戳或对显示列表去重来掩盖问题。
工作台还提供两个原生会话变量:@after_at 和 @after_id。首页中两者都是 NULL,表示没有先前游标。SQL 用 IS NULL 检查这种缺失,而非 = NULL。否则,最早订单优先的下一行必须时间戳更晚,或者时间戳相同但 ID 更大。只用 placed_at > @after_at 会丢掉重复钟表读数处的 900008 和 900009。
保留已有 group、state 和半开边界。将游标分支放在括号内,通过 AND 与其他筛选相连;OR 不能让行脱离其他业务条件。两个键仍保持升序。cat > queue.sql 用下面完整查询替换文件;带引号的 SQL here-document 标记防止 shell 展开,并独占一行结束。
cat > queue.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE shop_id=@group_id AND state=@state
AND placed_at>=@from_at AND placed_at<@until_at
AND (@after_at IS NULL
OR placed_at>@after_at
OR (placed_at=@after_at AND order_id>@after_id))
ORDER BY placed_at ASC,order_id ASC
LIMIT 7;
SQL
./desk page queue
./desk page queue --after "2025-05-10 09:15:00.000000" 900007
./desk plan queue --after "2025-05-10 09:15:00.000000" 900007
首页保持不变。下一页现在包含 900008、900009 和 900021,以及原始时间和金额。恰好位于下一个午夜的记录仍在请求日之外;最后一微秒仍在日内。计划应到达游标的索引范围,而不是读取并丢弃所有更早订单。一起检查实际行和诊断计数。
walk 操作重复运行同一保存查询,传递实际最后一行的游标,直到出现真实空页。它原样保留返回顺序和字段,不排序、不去重,也不补齐结果。查询重复返回上次游标时,工具会报错,不会把部分遍历显示为完整结果。
./desk walk queue
./desk page queue --after "2025-05-10 23:59:59.999999" 900021
遍历让十笔订单各交付一次:先七笔,再三笔,再空页。明确提供最后一行游标也返回空行列表。这是有效终点,不同于 Order desk error。文件或 SQL 错误时,用 ls 或 cat 检查,修正保存查询后重新运行真实请求。不要改变提供的索引或原始行。确认完整队列后运行步骤检查。
用有界工作量继续读取最新订单优先的历史
本步骤中,你将完成相反方向的游标,并把深页与相同数据的偏移量读取进行比较。
继续使用 Terminal 和 /home/labex/project/order-desk。保留修正后的取货查询及预备索引和索引源。顾客历史对时间和 ID 都使用 DESC,因此下一行的时间应更早,或时间相同而 ID 更小。顾客筛选和半开范围继续生效。首页 NULL 情况保持相同。
替换完整历史查询。不要添加 ready 状态限制:顾客历史包含该顾客所有原始状态和店铺。
cat > history.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE customer_id=@actor_id
AND placed_at>=@from_at AND placed_at<@until_at
AND (@after_at IS NULL
OR placed_at<@after_at
OR (placed_at=@after_at AND order_id<@after_id))
ORDER BY placed_at DESC,order_id DESC
LIMIT 7;
SQL
./desk page history
./desk page history --after "2025-05-10 09:15:00.000000" 900004
./desk walk history
首页以 900021 开始,再从 900009 递减到 900004。下一页包含 900003、900002 和 900001。完整遍历保留全部十笔订单,然后到达实际空页。零金额订单仍在首页;时间或价格相同绝不能合并标识。
较深的 OFFSET 要求 MySQL 在排序后丢弃前面的行。它可能返回七行正确结果,却读取更多行。游标可以改为从实际最后键附近开始。以顾客 77 从一月到四月的历史比较两者,不包含的结束日期为 5 月 1 日。
下面原生客户端中,LIMIT 1 OFFSET 199 跳过 199 行有序结果,返回第 200 行,即 OFFSET 200 页面前的最后一行。EXPLAIN ANALYZE 实际运行偏移查询并报告迭代器工作量。最终 SELECT 显示该页真实行。here-document 和两个排序方向与之前练习相同。
./db <<'SQL'
SELECT order_id,placed_at
FROM orders
WHERE customer_id=77
AND placed_at>='2025-01-01' AND placed_at<'2025-05-01'
ORDER BY placed_at DESC,order_id DESC
LIMIT 1 OFFSET 199;
EXPLAIN ANALYZE FORMAT=TREE
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE customer_id=77
AND placed_at>='2025-01-01' AND placed_at<'2025-05-01'
ORDER BY placed_at DESC,order_id DESC
LIMIT 7 OFFSET 200;
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE customer_id=77
AND placed_at>='2025-01-01' AND placed_at<'2025-05-01'
ORDER BY placed_at DESC,order_id DESC
LIMIT 7 OFFSET 200;
SQL
第 200 行是订单 94263,时间为 2025-02-25 02:30:00.000000。读取下层迭代器的实际行数:偏移请求必须先经过前 200 行,才能交付七行。现在把这个实际行作为保存查询的游标,使用相同顾客和边界。--from 和 --until 仅用于本次调用。
./desk page history --from 2025-01-01 --until 2025-05-01 --after "2025-02-25 02:30:00.000000" 94263
./desk plan history --from 2025-01-01 --until 2025-05-01 --after "2025-02-25 02:30:00.000000" 94263
相同七个 ID 出现,前两个为 3542 和 87067。游标查询应以较小实际请求数到达索引范围,而不是丢弃前 200 行。应比较同一页面的工作量,而不是两个无关请求;以毫秒计的耗时仍不能作为验收依据。
最后检查另一位顾客、有效空范围,以及未改变的取货遍历:
./desk page history --actor 78
./desk page history --from 2027-01-01 --until 2027-01-02
./desk walk queue
保留两个可复用 SQL 文件。提供的索引和 indexes.sql 保持完整。查询的 SQL、文件或连接错误仍是错误;读取工具不编造缺失订单。这些是准备好的固定快照上的稳定遍历,不是并发变更中的事务快照。最终检查验证精确的原始字段和顺序、小规模完整遍历、变化的身份、范围和游标、诊断工作量,以及源数据和工具的保留。
总结
你通过完整的时间戳和 ID 游标继续读取两个已索引订单视图,保留每笔同一时间订单和零金额值,并验证了真实空终点。你用 OFFSET 和索引游标请求比较同一个深页,而不将毫秒数视为证明。在原始数据、工具和预备索引保持完整的固定快照上,两个方向都正常工作。



