Skip to content

docs/design: add proposal for SELECT ... FOR UPDATE SKIP LOCKED#69783

Open
takaidohigasi wants to merge 2 commits into
pingcap:masterfrom
takaidohigasi:docs/skip-locked-design
Open

docs/design: add proposal for SELECT ... FOR UPDATE SKIP LOCKED#69783
takaidohigasi wants to merge 2 commits into
pingcap:masterfrom
takaidohigasi:docs/skip-locked-design

Conversation

@takaidohigasi

@takaidohigasi takaidohigasi commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: ref #18207

Problem Summary:

SELECT ... FOR UPDATE SKIP LOCKED (MySQL 8.0.1+) is a long-requested feature (#18207, feature/accepted). An earlier proposal (#32625) was never merged and predates the modern pessimistic lock protocol (wake_up_mode, per-key PessimisticLockKeyResult, resumable lock requests).

What changed and how does it work?

Adds docs/design/2026-07-12-select-for-update-skip-locked.md, a design proposal covering:

  • Semantics: skip only keys locked by other transactions (KeyIsLocked); write conflicts on unlocked keys still fail and retry at a newer for_update_ts (recomputing the skip set, matching MySQL's documented nondeterminism); FOR SHARE SKIP LOCKED and multi-table joins out of scope for v1.
  • Protocol: new PessimisticLockRequest.skip_locked field + LockResultSkipped per-key result type; per-key results in WakeUpModeNormal; multi-key batches allowed (skip never waits or abandons the request); incompatible with allow_lock_with_conflict.
  • TiKV: skip-and-continue in AcquirePessimisticLock (no clear-and-break, no lock-wait queue involvement); shared locks skipped without shrink-only conversion.
  • client-go: LockCtx.SkipLocked + skipped-key reporting; primary-key safety (a skipped primary with locked secondaries would let other transactions resolve those locks as rolled back, so candidate primaries are locked one by one); PessimisticRollbackKeys for the unique-index atomic group.
  • TiDB: planner enablement gated behind tidb_enable_select_skip_locked (default OFF, never silent no-op); SelectLockExec buffer-lock-filter mode; PointGet/BatchPointGet skips with index-key lock rollback.
  • Compatibility rollout (an old TiKV would silently ignore the field and wait): sysvar gate, TiKV min-version check, and a client-side hard error when the response lacks per-key results.
  • Test design (including the scenario catalog from MySQL's own MTR suites) and known v1 divergences.

A full working implementation across kvproto/TiKV/client-go/TiDB exists and passes the queue-worker pattern and skip semantics against a patched real TiKV cluster; implementation PRs will follow this design discussion. Preparatory bug-fix PR: #69782.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

None

Summary by CodeRabbit

  • Documentation
    • Added a new design document proposing support for MySQL-compatible SELECT ... FOR UPDATE SKIP LOCKED in TiDB.
    • Describes how skipped locked rows would be handled during pessimistic locking, including conflict behavior and defined scope exclusions.
    • Outlines protocol/client and execution-layer changes needed to ensure correct results, plus rollout/compatibility safeguards and a plan for testing and performance validation.

Supersedes the unmerged 2022 proposal (pingcap#32625), redesigned on top of
the modern pessimistic lock protocol (wake_up_mode, per-key
PessimisticLockKeyResult): a new skip_locked request field with a
LockResultSkipped per-key result, skip-on-KeyIsLocked-only semantics
(write conflicts still retry), primary-key safety in client-go, and a
three-layer compatibility rollout.

ref pingcap#18207

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ti-chi-bot ti-chi-bot Bot added the release-note-none Denotes a PR that doesn't merit a release note. label Jul 12, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 12, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign overvenus for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added the contribution This PR is from a community contributor. label Jul 12, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 12, 2026

Copy link
Copy Markdown

Hi @takaidohigasi. Thanks for your PR.

I'm waiting for a pingcap member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot ti-chi-bot Bot added needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 12, 2026
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 62bd6fd7-ee10-40f2-b888-f5dcdfe63e79

📥 Commits

Reviewing files that changed from the base of the PR and between 6c29bd3 and 63afa46.

📒 Files selected for processing (1)
  • docs/design/2026-07-12-select-for-update-skip-locked.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/design/2026-07-12-select-for-update-skip-locked.md

📝 Walkthrough

Walkthrough

Adds a TiDB design proposal for SELECT ... FOR UPDATE SKIP LOCKED, covering TiKV lock semantics, client-go plumbing, TiDB execution, compatibility safeguards, and validation plans.

Changes

Skip-locked pessimistic reads

Layer / File(s) Summary
Locking contract and TiKV behavior
docs/design/2026-07-12-select-for-update-skip-locked.md
Defines per-key skip semantics, kvproto protocol fields, response contracts, and TiKV lock-processing behavior.
Client lock handling and rollback
docs/design/2026-07-12-select-for-update-skip-locked.md
Specifies client-go propagation, legacy-response failures, accounting updates, and selective pessimistic rollback.
Planner and executor integration
docs/design/2026-07-12-select-for-update-skip-locked.md
Describes planner treatment, executor buffering and filtering, unique-index rollback, and write-conflict retry behavior.
Compatibility, testing, and operational boundaries
docs/design/2026-07-12-select-for-update-skip-locked.md
Covers version checks, test and benchmark plans, risks, alternatives, and unresolved questions.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • pingcap/tidb#69720 — Directly relates to end-to-end FOR UPDATE SKIP LOCKED support that skips locked rows.

Suggested reviewers: cfzjywx, ekexium, myonkeminta

Poem

A rabbit hops where locked rows sleep,
Skipping keys in a tidy heap.
TiKV whispers, “Pass this one,”
TiDB gathers what’s not gone.
No waiting maze, no needless fright—
A fluffy queue runs smooth and light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: a design proposal for SELECT ... FOR UPDATE SKIP LOCKED.
Description check ✅ Passed The description matches the template well, includes the issue reference, problem summary, change summary, checklist, and release note placeholder.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design/2026-07-12-select-for-update-skip-locked.md`:
- Around line 109-115: The skip_locked implementation must preserve
request-order mapping between mutations and per-key PessimisticLockKeyResult
entries, even when the TiKV path sorts mutations with ReturnValues disabled. Add
explicit original-index tracking or reorder results before returning, and add a
test covering unsorted multi-key input with mixed skipped/acquired statuses.
- Around line 109-115: Clarify the skip_locked contract in the design document
for non-skip errors occurring after earlier per-key results: specify whether the
server must return exactly one result per mutation by filling unprocessed
entries with LockResultFailed, or explicitly define client-go handling for
partial results alongside the error. Ensure the behavior applies to
WakeUpModeNormal and preserves request order.
- Around line 201-215: Update the upgrade-order and compatibility section of the
design to explicitly prevent TiKV downgrades below the minimum skip-locked
version, or enforce capability checks for every participating store before
issuing the request. Do not rely solely on the client-go response backstop,
since an incompatible store may block before returning; document the selected
enforcement mechanism and remove the claim that downgrades have no concern.
- Around line 184-188: Update the SelectLockExec skip-locked buffering design to
avoid unbounded child-row accumulation: implement a hard memory bound with
spill-to-disk or explicit rejection when the limit is reached, or revise the
documented execution strategy to guarantee bounded buffering. Ensure the
approach still filters rows after LockKeys with LockCtx.SkipLocked and remains
compatible with runPessimisticSelectForUpdate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 64aac78d-7019-4878-8d85-13687fb52098

📥 Commits

Reviewing files that changed from the base of the PR and between b94006d and 6c29bd3.

📒 Files selected for processing (1)
  • docs/design/2026-07-12-select-for-update-skip-locked.md

Comment on lines +109 to +115
Contract: when `skip_locked = true`, `wake_up_mode` must be `WakeUpModeNormal`, and the
response carries exactly one `PessimisticLockKeyResult` per mutation (in request order)
in `results`; the legacy `values`/`not_founds` fields stay empty. `skip_locked`
together with `allow_lock_with_conflict`/`WakeUpModeForceLock` is rejected. Unlike
`WakeUpModeForceLock`, multi-key batches are allowed: since skipped keys neither wait
nor abandon the request, each key's disposition is independent (the same key-by-key
model as `AcquirePessimisticLockResumed`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve request-order mapping for per-key results.

The contract requires results to be in request order, but the existing TiKV path sorts mutations when ReturnValues is false (pkg/store/mockstore/unistore/tikv/mvcc.go:237-239). Unless the new implementation preserves original indexes or restores the response order, skipped/acquired statuses can be applied to the wrong keys.

Define an explicit ordering mechanism and add a test with unsorted multi-key input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/2026-07-12-select-for-update-skip-locked.md` around lines 109 -
115, The skip_locked implementation must preserve request-order mapping between
mutations and per-key PessimisticLockKeyResult entries, even when the TiKV path
sorts mutations with ReturnValues disabled. Add explicit original-index tracking
or reorder results before returning, and add a test covering unsorted multi-key
input with mixed skipped/acquired statuses.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Specify per-key results for partial failures.

The document promises exactly one result per mutation, but also says non-skip errors fail the request unchanged. If a WriteConflict occurs after earlier keys have produced results, the response may be incomplete. Existing mockstore completion logic only applies to WakeUpModeForceLock (pkg/store/mockstore/unistore/tikv/mvcc.go:225-230), while this design requires WakeUpModeNormal.

Clarify whether the server must fill remaining entries with LockResultFailed, or define how client-go handles partial results on errors.

Also applies to: 128-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/2026-07-12-select-for-update-skip-locked.md` around lines 109 -
115, Clarify the skip_locked contract in the design document for non-skip errors
occurring after earlier per-key results: specify whether the server must return
exactly one result per mutation by filling unprocessed entries with
LockResultFailed, or explicitly define client-go handling for partial results
alongside the error. Ensure the behavior applies to WakeUpModeNormal and
preserves request order.

Comment thread docs/design/2026-07-12-select-for-update-skip-locked.md Outdated
Comment thread docs/design/2026-07-12-select-for-update-skip-locked.md Outdated
- Make the request-order mapping of per-key results an explicit contract
  requirement (with unsorted multi-key test coverage).
- Specify that request-level errors dominate partial results: the
  response carries errors, results stays empty, and clients process
  errors first and validate the results length otherwise.
- Document the SelectLockExec buffer bound: it counts against
  tidb_mem_quota_query and triggers the configured OOM action instead
  of growing unboundedly; disk spilling noted as follow-up work.
- Replace the "no downgrade concern" claim with explicit downgrade
  handling: per-statement min-version validation over all stores plus
  operational guidance to disable the feature before downgrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@takaidohigasi

Copy link
Copy Markdown
Contributor Author

Addressed the review comments in 63afa46:

  1. Request-order mapping — now an explicit contract requirement in the Protocol section: results[i] corresponds to mutations[i]; implementations must not reorder mutations for skip-locked requests (the unistore mockstore disables its sortMutations optimization in this mode). Added a TiKV unit-test case covering unsorted multi-key input [k9, k7, k8] with a mixed skipped/acquired outcome.
  2. Errors vs partial results — the contract now states that request-level errors dominate: on any non-skip error (e.g. WriteConflict) the response carries errors, no lock of the request is written, and results stays empty and is ignored; the client processes errors first and validates len(results) == len(mutations) otherwise.
  3. Downgrade handling — removed the "no downgrade concern" claim. The min-version check validates all stores per statement (TTL-cached), and the doc now states the operational requirement to disable tidb_enable_select_skip_locked before downgrading any TiKV below the feature's minimum version, since the client-side backstop alone cannot prevent an incompatible store from waiting.
  4. Buffer bound — documented that the SelectLockExec skip-locked buffer is attached to the statement memory tracker, counts against tidb_mem_quota_query, and triggers the configured OOM action (cancellation by default) rather than growing unboundedly; disk spilling is noted as follow-up work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution This PR is from a community contributor. needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant