ページをまたいですべての注文を保つ

MySQLBeginner
オンラインで実践に進む

はじめに

同僚が、店の最初のページ用の 2 つの検索をすでに調整しました。受け取りキューは古い順、顧客履歴は新しい順です。今度は呼び出し側が最初の 7 注文より先を取得する必要があります。実際の複数の注文が同じタイムスタンプを持つため、日時だけのカーソルではレコードを失ったり繰り返したりする場合があります。

この独立した単元は、独自の MySQL スナップショット、動作する最初のページ用の queue.sql と history.sql、適用済みの索引と indexes.sql を提供します。前の VM のファイルは不要です。効率的な索引アクセスを保ちつつ、昇順・降順の全ページ取得で元のすべての注文が残るよう、保存済みの 2 クエリを編集します。

前提は、最初の単元の MySQL パラメーター、半開の境界、2 列の並び順、診断用の計画です。NULL を取り得るカーソル、完全な比較、深いページの確認はここで学びます。Terminal と /home/labex/project/order-desk を使います。変更するのは queue.sql と history.sql だけです。提供された索引と索引ソース、すべての注文の事実、列、主キー、実行ツールを保全してください。

各ページは引き続き最大 7 件の実際の注文を含み、ネイティブ診断行リクエストを最大 64 回使います。準備済みの接続では、この制御された比較のためにインデックスコンディションプッシュダウンを無効にしています。その設定を保ち、オプティマイザーヒントは使いません。リクエストは固定のデータスナップショットを使います。同時更新時のスナップショット、物理 I/O 回数、本番環境の遅延の保証はありません。実行環境のラベルと出力は英語のままです。

両キーを次の受け取りページへ引き継ぐ

このステップでは、同じタイムスタンプの残りの注文を失わずに、古い順のキューを先へ進めます。

Terminal の通常のシェルから始めます。提供された db と desk は、最初の単元で練習したものと同じインターフェースです。パスやファイル名が誤っていたら、ls で実際の名前を確認し、元の行を変えずに再試行します。ここでの cat は保存したクエリのテキストを表示します。続く page リクエストは、実際の出力を読みます。正解用のクエリが欠落行を補うことはありません。

cd /home/labex/project/order-desk
cat queue.sql
./desk page queue

保存済みクエリには、有用な店舗・状態・時間の条件と (placed_at,order_id) の並び順がありますが、カーソル条件がありません。最初のページは、900001~900007 を正しく返します。desk の cursor_for_next_request は、実際の最後の行の日時と ID を含みます。次のリクエストを始める位置を示しますが、次のページに行があることを約束するものではありません。

--after は、実際の最後のタイムスタンプと、実際の最後の注文 ID の 2 引数を取ります。引用符はタイムスタンプ内の空白を 1 つのシェル引数に保ちます。先ほど観察したカーソルで次のページを要求します。

./desk page queue --after "2025-05-10 09:15:00.000000" 900007

変更していない初期クエリはカーソルを無視するため、最初の 7 ID を再び返します。コマンドはすべて成功していますが、次のページとしては誤りです。問題を隠すために元のタイムスタンプを変えたり、表示した一覧を重複除去したりしないでください。

desk は、追加のネイティブセッション変数 @after_at と @after_id を渡します。最初のページでは、前のカーソルがないことを意味する NULL が両方に入ります。SQL では、その不在を = NULL ではなく IS NULL で調べます。それ以外では、古い順の次の行は、より後の日時、または同じ日時でより大きい ID を持つ必要があります。placed_at > @after_at だけを使うと、繰り返される同時刻の ID 900008 と 900009 を失います。

既存のグループ、状態、半開の境界を保ちます。カーソルの選択肢を括弧内に置き、AND でそれらのフィルターに結び付けます。OR により、他の業務条件を外れた行が通らないようにします。両キーとも昇順のままです。cat > queue.sql は、以下の完全なクエリで置き換えます。引用符付きの SQL ヒアドキュメントマーカーはシェル展開を防ぎ、単独行で閉じます。

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 が含まれるようになります。翌日の午前 0 時ちょうどは、引き続き指定日の範囲外で、最後のマイクロ秒は範囲内です。計画は、それ以前のすべての注文を読んで捨てるのではなく、カーソルの索引範囲に到達するはずです。実際の行と診断回数を合わせて確認します。

walk アクションは、実際の最後の行のカーソルを渡し、同じ保存済みクエリを本当の空ページが出るまで繰り返し実行します。返された順序とフィールドを厳密に保ち、並べ替え、重複除去、結果の補充はしません。クエリが最後のカーソルを繰り返すと、途中の取得を完全なものとして見せる代わりに、ツールはエラーを報告します。

./desk walk queue
./desk page queue --after "2025-05-10 23:59:59.999999" 900021

全ページ取得では、7 件、次に 3 件、次に空ページとなり、全 10 注文をちょうど 1 回ずつ取得します。最後の行のカーソルを明示しても空の行リストが返ります。これは Order desk error と違い、正当な終端です。ファイルや SQL が誤っていれば、ls や cat で調べ、保存したクエリを修正して実際のリクエストを再実行します。提供された索引や元の行は変えません。キュー全体を確認してから、ステップのチェックを実行します。

作業量を抑えて新しい順の履歴を続ける

このステップでは、逆方向のカーソルを完成させ、同じデータに対するオフセット方式の読み取りと、深いページを比較します。

Terminal の /home/labex/project/order-desk で続けます。修正した受け取りクエリと準備済みの索引・索引ソースを保ちます。顧客履歴は日時と ID の両方に DESC を使うため、次の行はより前の日時、または同じ日時でより小さい ID を持ちます。顧客フィルターと半開区間は引き続き有効です。最初のページの NULL の扱いも同じです。

履歴クエリ全体を置き換えます。準備済み状態の制限は加えません。顧客履歴は、その顧客のすべての元の状態・店舗を含みます。

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 を含みます。全ページ取得は 10 注文をすべて保ち、その後、実際の空ページに到達します。無料注文は最初のページに残り、同時刻や同額で識別情報が統合されることはありません。

深い OFFSET は、並べ替えた後に前方の行を捨てるよう MySQL に要求します。正しい 7 行を返しても、はるかに多く読む場合があります。カーソルなら、実際の最後のキーの近くから始められます。5 月 1 日を排他的な終了とする、顧客 77 の 1~4 月の履歴で比較します。

以下のネイティブクライアントで、LIMIT 1 OFFSET 199 は、並べた 199 行を飛ばして 200 行目を返します。これは OFFSET 200 のページの直前の行です。EXPLAIN ANALYZE はオフセットクエリを実際に実行し、イテレーターの作業量を報告します。最後の SELECT は、そのページの実際の行を表示します。ヒアドキュメントと両方向の並び順は、練習済みのものと同じです。

./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 行目は、2025-02-25 02:30:00.000000 の注文 94263 です。下位イテレーターの実際の行数を読んでください。オフセットリクエストは、7 行を返す前に、最初の 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

3542 と 87067 から始まる、同じ 7 ID が現れます。カーソルクエリは、前の 200 行を捨てるのではなく、少ない実リクエスト数で索引範囲に到達するはずです。無関係な 2 リクエストではなく、同じページの作業量を比較してください。経過ミリ秒だけでは、やはり合格を示せません。

最後に、別の顧客、正当な空範囲、変更していない受け取りキューの全ページ取得を確認します。

./desk page history --actor 78
./desk page history --from 2027-01-01 --until 2027-01-02
./desk walk queue

再利用可能な両 SQL ファイルを保管します。提供された索引と indexes.sql はそのまま保ちます。クエリの SQL・ファイル・接続エラーはエラーのままであり、読み取り側は欠けた注文を作り出しません。これは準備済みの固定スナップショット上の安定した全ページ取得であり、同時変更中のトランザクションスナップショットではありません。最終チェックは、元の正確なフィールド・順序、小規模な完全取得、変更された識別情報・範囲・カーソル、診断上の作業量と、元データ・ツールの保全を確認します。

まとめ

完全な日時・ID カーソルで索引付きの 2 つの注文表示の続きを取得し、同時刻のすべての注文と 0 セントの値を保ち、実際の空の終端を確認しました。ミリ秒を証拠とせず、OFFSET と索引付きカーソルのリクエストで同じ深いページを比較しました。元のデータ、ツール、準備済みの索引を保全したまま、固定スナップショット上で両方向が動作します。