Skip to content

docs: document partial indexes for ADD INDEX - #21950

Open
YangKeao wants to merge 1 commit into
pingcap:masterfrom
YangKeao:docs/add-index-partial-index
Open

YangKeao wants to merge 1 commit into
pingcap:masterfrom
YangKeao:docs/add-index-partial-index

Conversation

@YangKeao

@YangKeao YangKeao commented Sep 22, 2026

Copy link
Copy Markdown
Member

What is changed, added, or deleted? (Required)

Document partial-index syntax, scenarios, ALTER TABLE ... ADD INDEX examples, predicate behavior, and limitations on the ADD INDEX reference page.

Which TiDB version(s) do your changes apply to? (Required)

Tips for choosing the affected version(s):

By default, CHOOSE MASTER ONLY so your changes will be applied to the next TiDB major or minor releases. If your PR involves a product feature behavior change or a compatibility change, CHOOSE THE AFFECTED RELEASE BRANCH(ES) AND MASTER.

For details, see tips for choosing the affected versions (in Chinese).

  • master (the latest development version)
  • v9.0 (TiDB 9.0 versions)
  • v8.5 (TiDB 8.5 versions)
  • v8.1 (TiDB 8.1 versions)
  • v7.5 (TiDB 7.5 versions)
  • v7.1 (TiDB 7.1 versions)
  • v6.5 (TiDB 6.5 versions)

What is the related PR or file link(s)?

AI agent involvement

  • The changes in this PR were primarily made by an AI agent on behalf of the PR author.

Do your changes match any of the following descriptions?

  • Delete files
  • Change aliases
  • Need modification after applied to another branch
  • Might cause conflicts after applied to another branch

Summary by CodeRabbit

  • 新功能
    • ADD INDEX 语法新增 WHERE Expression 选项,支持创建部分索引。
    • 新增普通及唯一部分索引的创建和查询使用示例。
    • 补充部分索引适用场景及使用限制说明。

@ti-chi-bot

ti-chi-bot Bot commented Sep 22, 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 csuzhangxc 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 missing-translation-status This PR does not have translation status info. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Sep 22, 2026
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

文档为 ADD INDEX 增加部分索引语法、创建示例、查询示例和使用限制,适用于 v8.5.7 和 v9.0.0 及更高版本。

Changes

部分索引文档

Layer / File(s) Summary
部分索引语法与创建示例
sql-statements/sql-statement-add-index.md
IndexOption 新增 WHERE Expression 语法。文档说明部分索引的适用场景,并加入普通部分索引和唯一部分索引的创建示例。
部分索引查询行为与限制
sql-statements/sql-statement-add-index.md
文档展示满足索引谓词时的索引扫描行为,以及谓词不匹配时即使使用 hint 也执行表扫描的情况。文档列出支持的谓词类型、类型一致性、同表列引用和表达式索引限制。

Priority: ⬇️ Low

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Other

Merge Risk: 🔵 Low · up to acb35

Users may encounter failed DDL when following the current documentation; correct the limitation list before merging.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了主要变更,即为 ADD INDEX 文档补充部分索引说明。标题简洁、明确,并与变更内容一致。
Description check ✅ Passed PR 描述包含变更内容、适用版本、相关链接、AI 参与情况和其他检查项。内容与 PR 目标一致,必填信息完整。首次贡献者清单为条件项,未填写不影响本次检查。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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 a new PR

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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: pingcap/docs-cn/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: cfc296cc-d8f8-405d-8d5e-12327f14f6f0

📥 Commits

Reviewing files that changed from the base of the PR and between 46c3a8f and acb359f.

📒 Files selected for processing (1)
  • sql-statements/sql-statement-add-index.md

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment on lines +231 to +234
- 部分索引的 `WHERE` 子句支持基本比较运算符(`=`、`!=`、`<`、`<=`、`>`、`>=`)、`IS NULL`、`IS NOT NULL` 和包含常量值的 `IN` 谓词。
- 谓词中的列和常量值必须具有相同的数据类型。
- 谓词只能引用同一表中的列。
- 不能在表达式索引上创建部分索引。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '220,242p' sql-statements/sql-statement-add-index.md
rg -n "partial index|PartialIndex|WHERE.*Expression|generated column|partition" . --glob '!node_modules' --glob '!vendor' | head -200

Repository: pingcap/docs-cn

Length of output: 26995


🏁 Script executed:

python3 - <<'PY'
import json
import urllib.request

for ref in ("v8.5.7", "v9.0.0"):
    url = f"https://api.github.com/repos/pingcap/tidb/git/trees/{ref}?recursive=1"
    req = urllib.request.Request(url, headers={"User-Agent": "review-inspection"})
    with urllib.request.urlopen(req) as r:
        data = json.load(r)
    print(f"## {ref}")
    for item in data.get("tree", []):
        path = item.get("path", "")
        if any(term in path.lower() for term in ("partial", "index_test", "ddl/index")):
            print(path)
PY

Repository: pingcap/docs-cn

Length of output: 2439


🏁 Script executed:

set -eu
for ref in v8.5.7 v9.0.0; do
  echo "## $ref: pkg/ddl/index.go"
  curl -skL "https://raw.githubusercontent.com/pingcap/tidb/$ref/pkg/ddl/index.go" |
    nl -ba | grep -i -E -C 8 'partial|generated|partition|predicate' | head -240
  echo "## $ref: candidate test paths"
  curl -skL "https://api.github.com/repos/pingcap/tidb/git/trees/$ref?recursive=1" |
    grep -oE '"path": "[^"]+"' |
    grep -i -E 'partial|ddl/.+index|index.+test' | head -100
done

Repository: pingcap/docs-cn

Length of output: 15444


🏁 Script executed:

set -eu
for ref in v8.5.7 v9.0.0; do
  echo "## ref status: $ref"
  curl -skL -o /tmp/tidb-ref-check -w 'HTTP %{http_code} bytes %{size_download}\n' "https://raw.githubusercontent.com/pingcap/tidb/$ref/pkg/ddl/index.go"
  echo "## focused test: $ref"
  curl -skL -o /tmp/tidb-partial-test -w 'HTTP %{http_code} bytes %{size_download}\n' "https://raw.githubusercontent.com/pingcap/tidb/$ref/tests/integrationtest/t/planner/core/casetest/index/partialindex.test"
  if [ -s /tmp/tidb-partial-test ]; then
    nl -ba /tmp/tidb-partial-test | sed -n '1,260p'
  fi
  echo "## focused result: $ref"
  curl -skL -o /tmp/tidb-partial-result -w 'HTTP %{http_code} bytes %{size_download}\n' "https://raw.githubusercontent.com/pingcap/tidb/$ref/tests/integrationtest/r/planner/core/casetest/index/partialindex.result"
  if [ -s /tmp/tidb-partial-result ]; then
    nl -ba /tmp/tidb-partial-result | sed -n '1,320p'
  fi
done

echo "## v8.5.7 source candidates"
for path in pkg/ddl/ddl_api.go pkg/ddl/index.go pkg/ddl/index_modify_test.go pkg/ddl/index_change_test.go; do
  echo "### $path"
  curl -skL "https://raw.githubusercontent.com/pingcap/tidb/v8.5.7/$path" |
    nl -ba | grep -i -E -C 10 'partial|condition|generated|partition' | head -300
done

Repository: pingcap/docs-cn

Length of output: 37204


🏁 Script executed:

set -eu
rm -rf /tmp/tidb-v857-src
mkdir -p /tmp/tidb-v857-src
curl -skL "https://codeload.github.com/pingcap/tidb/tar.gz/refs/tags/v8.5.7" |
  tar -xz -C /tmp/tidb-v857-src --strip-components=1
echo "## v8.5.7 condition implementation"
rg -n -C 12 'CheckAndBuildIndexConditionString|ConditionExprString|ErrUnsupportedAddPartialIndex|HasCondition' /tmp/tidb-v857-src/pkg /tmp/tidb-v857-src/tests/integrationtest/t /tmp/tidb-v857-src/tests/integrationtest/r | head -500
echo "## v8.5.7 partial-related tests"
rg -n -i -C 8 'partial index|partialindex|where .* (and|or|in)|generated.*where|partition.*where|where .*generated' /tmp/tidb-v857-src/pkg /tmp/tidb-v857-src/tests/integrationtest/t /tmp/tidb-v857-src/tests/integrationtest/r | head -500

echo "## candidate v9 refs"
curl -skL "https://api.github.com/repos/pingcap/tidb/git/matching-refs/heads/release-9" | grep -oE '"ref": "[^"]+"' | head -20 || true
curl -skL "https://api.github.com/repos/pingcap/tidb/git/matching-refs/tags/v9" | grep -oE '"ref": "[^"]+"' | head -40 || true
for ref in release-9.0 release-9.0.0 release-9; do
  code=$(curl -skL -o /tmp/tidb-v9-index -w '%{http_code}' "https://raw.githubusercontent.com/pingcap/tidb/$ref/pkg/ddl/index.go")
  echo "$ref HTTP $code bytes $(wc -c </tmp/tidb-v9-index)"
done

Repository: pingcap/docs-cn

Length of output: 41871


🏁 Script executed:

set -eu
echo "## validator locations"
rg -l 'CheckAndBuildIndexConditionString' /tmp/tidb-v857-src/pkg /tmp/tidb-v857-src/tests | head -30
echo "## validator definition and callers"
rg -n -C 35 'func CheckAndBuildIndexConditionString|CheckAndBuildIndexConditionString\(' /tmp/tidb-v857-src/pkg/ddl /tmp/tidb-v857-src/pkg | head -260
echo "## direct partial-index DDL tests"
rg -n -C 12 -i 'create table .*where|add .*index .*where|partial index|unsupported.*partial|where .* in \(|where .* and .*|where .* or .*' \
  /tmp/tidb-v857-src/tests/integrationtest/t/ddl \
  /tmp/tidb-v857-src/tests/integrationtest/t/planner/core/casetest/index \
  /tmp/tidb-v857-src/pkg/ddl \
  /tmp/tidb-v857-src/pkg/planner | head -400

Repository: pingcap/docs-cn

Length of output: 41947


🏁 Script executed:

set -eu
nl -ba /tmp/tidb-v857-src/pkg/ddl/index.go | sed -n '3769,3945p'

Repository: pingcap/docs-cn

Length of output: 9538


🏁 Script executed:

set -eu
nl -ba /tmp/tidb-v857-src/pkg/ddl/index.go | sed -n '3940,3995p'
echo "## v9 stable/tag refs matching 9.0.0"
curl -skL "https://api.github.com/repos/pingcap/tidb/git/refs?ref=tags/v9.0.0" | head -80

Repository: pingcap/docs-cn

Length of output: 6504


🏁 Script executed:

set -eu
echo "## v8.5.7 expression/partial interaction"
rg -n -i -C 10 'partial.*(expression|functional)|(?:expression|functional).*partial|HasCondition\(\).*Hidden|Hidden.*HasCondition' \
  /tmp/tidb-v857-src/pkg/ddl /tmp/tidb-v857-src/pkg/table /tmp/tidb-v857-src/tests/integrationtest/t | head -300
echo "## v8.5.7 index construction around expression columns"
rg -n -C 18 'func buildIndexColumns|func BuildIndexInfo|GeneratedExprString|IndexPartSpecification.*Expr' \
  /tmp/tidb-v857-src/pkg/ddl/index.go | head -420
echo "## v9 beta validator comparison"
for ref in v9.0.0-beta.2 release-9.0-beta.2 master; do
  echo "### $ref"
  curl -skL "https://raw.githubusercontent.com/pingcap/tidb/$ref/pkg/ddl/index.go" |
    grep -n -E -A95 -B5 'func CheckAndBuildIndexConditionString|func checkIndexCondition' | head -220
done

Repository: pingcap/docs-cn

Length of output: 15760


🏁 Script executed:

set -eu
echo "## add-index expression/partial guards"
rg -n -C 18 'hiddenCols|Condition.*hidden|hidden.*Condition|ExpressionIndex|expression index|functional index' \
  /tmp/tidb-v857-src/pkg/ddl/executor.go /tmp/tidb-v857-src/pkg/ddl/index.go /tmp/tidb-v857-src/pkg/ddl | head -500
echo "## partial-index DDL examples involving expression index"
rg -n -i -C 8 'where[[:space:]].*(\(\(|generated)|\(\(.*\)\).*where|where.*\(\(' \
  /tmp/tidb-v857-src/tests /tmp/tidb-v857-src/pkg/ddl | head -300

Repository: pingcap/docs-cn

Length of output: 41951


🌐 Web query:

TiDB v9.0.0 partial index CheckAndBuildIndexConditionString pkg/ddl/index.go

💡 Result:

<source_evidence>

<title>CREATE INDEX | TiDB SQL Statement Reference | TiDB Docs</title> https://docs.pingcap.com/tidb/stable/sql-statement-create-index/ ## Partial indexes New in v8.5.7 ... A partial index is an index built on a subset of rows in a table. When creating a partial index, you can specify a conditional expression, also known as a predicate, to define that subset of rows. The index contains entries only for the rows that satisfy the predicate. ... ### Create partial indexes ... You can create partial indexes by adding a `WHERE` clause to the index definition. For example: ... CREATE TABLE t1 (c1 INT, c2 INT, c3 TEXT); CREATE INDEX idx1 ON t1 (c1) WHERE c2 > 10; ... You can also create partial indexes using `ALTER TABLE`: ... ALTER TABLE t1 ADD INDEX idx2 (c1, c2) WHERE c3 = &`#39`;abc&`#39`;; You can also specify a partial index when you create the table: ... CREATE TABLE t2 ( id INT PRIMARY KEY, status VARCHAR(20), created_at DATETIME, INDEX idx_active_status (status) WHERE status = &`#39`;active&`#39`; ); ... ### Usage examples ... Create partial indexes for common query patterns: ... CREATE INDEX idx_active_users ON users (name) WHERE status = &`#39`;active&`#39`;; CREATE INDEX idx_high_score_users ON users (created_at) WHERE score > 1000; CREATE INDEX idx_pending_status ON users (status) WHERE status = &`#39`;pending&`#39`;; ... Then the following queries can use the partial index: ... mysql> EXPLAIN SELECT * FROM users WHERE status = &`#39`;active&`#39`; AND name = &`#39`;John&`#39`;; +-------------------------------+---------+-----------+-------------------------------------------+-------------------------------------------------------+ | id | estRows | task | access object | operator info | +-------------------------------+---------+-----------+-------------------------------------------+-------------------------------------------------------+ | IndexLookUp_9 | 1.00 | root | | | | ├─IndexRangeScan_6(Build) | 10.00 | cop[tikv] | table:users, index:idx_active_users(name) | range:["John","John"], keep order: false, stats:pseudo | | └─Selection_8(Probe) | 1.00 | cop[tikv] | | eq(test.users.status, "active") | | └─TableRowIDScan_7 | 10.00 | cop[tikv] | table:users | keep order: false, stats:pseudo | +-------------------------------+---------+-----------+-------------------------------------------+-------------------------------------------------------+ 4 rows in set (0.00 sec) mysql> EXPLAIN SELECT * FROM users WHERE status = &`#39`;active&`#39`; ORDER BY name; +-------------------------------+----------+-----------+-------------------------------------------+---------------------------------+ | id | estRows | task | access object | operator info | +-------------------------------+----------+-----------+-------------------------------------------+---------------------------------+ | IndexLookUp_18 | 10.00 | root | | | | ├─IndexFullScan_15(Build) | 10000.00 | cop[tikv] | table:users, index:idx_active_users(name) | keep order: true, stats:pseudo | | └─Selection_17(Probe) | 10.00 | cop[tikv] | | eq(test.users.status, "active") | | └─TableRowIDScan_16 | 10000.00 | cop[tikv] | table:users | keep order: false, stats:pseudo | +-------------------------------+----------+-----------+-------------------------------------------+---------------------------------+ 4 rows in set (0 ... 00 sec) mysql> EXPLAIN SELECT * FROM users WHERE ... > 100 ... 0 ORDER BY created_at; +-------------------------------+----------+-----------+-----------------------------------------------------+--------------------------------+ | id | estRows | task | access object | operator info | +-------------------------------+----------+-----------+------------------------------------------------ ... --------------------------------+ | IndexLookUp ... 8 | 3333.33 | root | | | | ├─IndexFullScan_15( ... ) | 1 ... 000.00 | cop[tikv] | table:users, index:idx_high_score_users( ... _at) | keep order: true, stats:pseudo | | └─Selection ... (Probe) | 3 ... 33.33 | cop ... v] | | gt(test. ... .score, ... 0000) | | ... ─TableRowID ... * FROM users WHERE status = &`#39`; ... …[truncated] <title>sql-statements/sql-statement-create-index.md</title> https://github.com/pingcap/docs/blob/master/sql-statements/sql-statement-create-index.md ## Partial indexes New in v8.5.7 and v9.0.0 A partial index is an index built on a subset of rows in a table. When creating a partial index, you can specify a conditional expression, also known as a predicate, to define that subset of rows. The index contains entries only for the rows that satisfy the predicate. ... ### Create partial indexes ... You can create partial indexes by adding a `WHERE` clause to the index definition. For example: ... ```sql CREATE TABLE t1 (c1 INT, c2 INT, c3 TEXT); CREATE INDEX idx1 ON t1 (c1) WHERE c2 > 10; ... You can also create partial indexes using `ALTER TABLE`: ... ```sql ALTER TABLE t1 ADD INDEX idx2 (c1, c2) WHERE c3 = &`#39`;abc&`#39`;; ... You can also specify a partial index when you create the table: ... ```sql CREATE TABLE t2 ( id INT PRIMARY KEY, status VARCHAR(20), created_at DATETIME, INDEX idx_active_status (status) WHERE status = &`#39`;active&`#39`; ); ... ### Usage examples ... Create partial indexes for common query patterns: ... ```sql CREATE INDEX idx_active_users ON users (name) WHERE status = &`#39`;active&`#39`;; CREATE INDEX idx_high_score_users ON users (created_at) WHERE score > 1000; CREATE INDEX idx_pending_status ON users (status) WHERE status = &`#39`;pending&`#39`;; ... If the predicate for a query does not satisfy the conditions defined by the partial index, TiDB does not select the partial index, even with a hint. For example, the following statement cannot use the partial index `idx_high_score_users`, because the query predicate `score > 100` does not satisfy the partial index definition `score > 1000`: ... ```sql mysql> EXPLAIN SELECT * FROM users USE INDEX(idx_high_score_users) WHERE score > 100 ORDER BY created_at; ... -----------+---------------+--------------------------------+ ... - The `WHERE` clause in partial indexes supports basic comparison operators (`=`, `!=`, `<`, `<=`, `>`, `>=`), `IS NULL`, `IS NOT NULL`, and `IN` predicates with constant values. - The columns and constant values in the predicate must be of the same data type. - The predicate can only reference columns from the same table. - Partial indexes cannot be created on expression indexes. <title>ddl,parser: modify schema to store the partial condition</title> GitHub pull request 62759 in pingcap/tidb (link omitted to avoid creating a cross-reference) # ddl,parser: modify schema to store the partial condition ... ### What problem does this PR solve? Issue Number: close `#62758` https://github.com/pingcap/tidb/pull/63448 -> https://github.com/pingcap/tidb/pull/62759 https://github.com/pingcap/tidb/pull/62762 ### What changed and how does it work? 1. Modify the parser to add support for partial index. The partial index is also part of the `IndexOption`. 2. Modify the schema to store the partial condition. The DDL will validate and convert the partial condition to a string and store it in the schema. 3. Add a new column `predicate` to the `tidb_indexes` table to show the index condition. 4. Modify the `ShowExec` to show the index condition. ### Check List Tests - [x] 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. ```release-note None ``` ... - Referenced by PR `#62762`: ddl,tables: only write the index when it meets partial index condition ... - Review requested from win ... - Review requested ... - Referenced by PR `#63448`: parser: modify parser to support partial index ... - YangKeao head_ref_force_pushed - YangKeao head_ref_force_p ... - YangKeao head_ ... _pushed - Renamed from "ddl,parser: add parser support for partial index, and modify schema to store the partial condition" to "ddl,parser: modify schema to store the partial condition" ... - ti-chi-bot[bot] merged - ti-chi-bot[bot] closed - Referenced by PR `#65698`: parser: modify parser to support partial index (`#63448`) - Referenced by PR `#65699`: ddl,parser: modify schema to store the partial condition (`#62759`) - Referenced in commit 7e5f28e - Referenced by PR `#65700`: ddl,tables: only write the index when it meets partial index condition (`#62762`) - Referenced by PR `#68823`: parser: modify parser to support partial index (`#63448`) - ti-chi-bot[bot] added label "needs-cherry-pick-release-8.5" - Referenced by PR `#68830`: parser: modify parser to support partial index (`#63448`) ... > In response to a cherrypick label: new pull request created to branch `release-8.5`: `#68831`. > But this PR has conflicts, please resolve them! - Referenced by PR `#68831`: ddl,parser: modify schema to store the partial condition (`#62759`) - Referenced by PR `#68833`: ddl,tables: only write the index when it meets partial index condition (`#62762`) - Referenced in commit d8da466 - Referenced in commit 2763499 <title>[Upgrade] Partial index predicate is silently lost when add-index DDL is queued as Job V1</title> GitHub issue 70281 in pingcap/tidb (link omitted to avoid creating a cross-reference) 4. Connect directly to the v8.5.7 TiDB and submit: ```sql CREATE INDEX idx_b_partial ON pi_upgrade_repro.t(b) WHERE a > 0; ``` The job is system-paused during the upgrade. In the reproduced run, the persisted job was `Version:v1`, had six V1 raw arguments, and had no persisted `ConditionString`. ... The original predicate must survive the upgrade pause/resume lifecycle and the completed index must remain: ... ```sql KEY `idx_b_partial` (`b`) WHERE `a` > 0 ... Only the two rows satisfying `a > 0` should have index entries. If Job V1 cannot represent the predicate, TiDB should reject the DDL with an actionable error instead of accepting it and changing its semantics. ... The DDL returned success without an error or warning, but the predicate was silently lost. The completed object was: ... `INFORMATION_SCHEMA.TIDB_INDEXES.PREDICATE` was `NULL`, and all four rows had committed index entries rather than only the two rows satisfying `a > 0`. ... Both `ADMIN CHECK TABLE` and `ADMIN CHECK INDEX` succeeded. They cannot detect this issue because the table and index are internally consistent as an ordinary full index; the failure is a silent change of the user&`#39`;s requested DDL semantics. ... ```text jobVersion=v1 ID:180, State:pausing, Version:v1, ArgLen:6, query="... WHERE a > 0" change job version in use: old=v1, new=v2 jobID=180, Version:v1, partial_condition_expr_string="" table scan count=4, totalKeys=4, writtenKeys=4 State=synced, RowCount=4, Version:v1 ``` ... - low version: TiDB v7.5.6 - high version: TiDB v8.5.7 - upgrade path: standard component-by-component rolling upgrade; PD and TiKV are upgraded before the TiDB rolling-upgrade phase - feature window: partial index support is introduced into the release-8.5 line in v8.5.7; relevant schema/DDL backport: https://github.com/pingcap/tidb/pull/68831 (master PR: https://github.com/pingcap/tidb/pull/62759) ... - old actor: a registered v7.5.6 TiDB, which causes the target submitter to keep using DDL Job V1 - new actor: the v8.5.7 TiDB that parses and submits the partial-index DDL, and later resumes the same job after all nodes are upgraded - shared object or contract: persisted `ModifyIndexArgs` for an add-index DDL job, especially `IndexArg.ConditionString` ... pause the job; finish ... rollout and resume ... Preserve the partial-index predicate end-to-end, or fail explicitly before persisting an unrepresentable Job V1. ... The predicate is absent from the persisted V1 arguments. The same job later completes successfully as a full index after the cluster is entirely on v8.5.7. ... The v8.5.7 submit path normalizes the predicate into `IndexArg.ConditionString`, but `ModifyIndexArgs.getArgsV1()` serializes only the legacy add-index arguments and omits `ConditionString`. The upgrade gate marks the job system-paused but still fills and persists its arguments while the process is using Job V1. When the same job is resumed after the rollout, `decodeAddIndexV1()` yields an empty condition, so the v8.5.7 worker consistently builds an ordinary full index. ... Suggested guard: reject partial add-index DDL when `job.Version == JobVersion1`, or provide a backward-compatible encoding that preserves the predicate. Add a regression covering `V1 submit + upgrade system pause + all-target V2 readiness + resume same job`. ... > Code-level finding (verified against GitHub-hosted source; no local checkout was used). > > The reported root cause is confirmed on the exact target commit, `202b7f47286a1109b5c957401d34c9358d130ae0`: > > 1. **The mixed-version registry selects Job V1.** The Job V2 cutoff is 8.4.0, and `detectAndUpdateJobVersionOnce()` scans every registered TiDB. If any node is older than 8.4.0, `allSupportV2` becomes false and `targetVer` remains `JobVersion1`: ddl.go#L92-L96, ddl.go#L1000-L1046. > > 2. **ADD INDEX inherits that global version, while the predicate is initially present.** The submit path assigns `job.Ve…[truncated] <title>ddl,tables: only write the index when it meets partial index condition · Pull Request `#62762` · pingcap/tidb</title> GitHub pull request 62762 in pingcap/tidb (link omitted to avoid creating a cross-reference) ## ddl,tables: only write the index when it meets partial index condition ... 1. Modify the `tables.index` to parse/store the expression of `partialConditionExpression`. 2. Add a `MeetPartialCondition` to check whether it&`#39`;ll meet the condition. 3. Add some tests about the DML during the DDL on the same table. 4. Modify the row count of `admin show ddl job` from the added count to the scanned count. 5. Support `admin check table` for partial index. ... **Yang Ke ... commit `5 ... 90`: support writing data / deleting data ... · Nov 1 ... , 2025 at 3:34am ... **YangKeao** mentioned this in PR [`#65700`: ddl,tables: only write the index when it meets partial index condition (`#62762`)](https://github.com/pingcap/tidb/pull/65700) · Jan 21, 2026 at 9:37am

Citations:


更正部分索引谓词限制。

实现仅支持单个简单谓词。IN 以及使用 ANDOR 组合的条件均不受支持。类型检查允许兼容类型,不要求完全相同。生成列和分区表不支持部分索引。

可直接应用的替换内容
-- 部分索引的 `WHERE` 子句支持基本比较运算符(`=`、`!=`、`<`、`<=`、`>`、`>=`)、`IS NULL`、`IS NOT NULL` 和包含常量值的 `IN` 谓词。
-- 谓词中的列和常量值必须具有相同的数据类型。
-- 谓词只能引用同一表中的列。
-- 不能在表达式索引上创建部分索引。
+- 部分索引的 `WHERE` 子句仅支持单个简单谓词:基本比较运算符(`=`、`!=`、`<`、`<=`、`>`、`>=`)、`IS NULL` 或 `IS NOT NULL`。不支持 `IN`,也不支持使用 `AND` 或 `OR` 组合多个条件。
+- 比较运算符两侧的列和字面量必须具有兼容的数据类型,不要求类型完全相同。
+- 谓词只能引用同一表中的可见、非生成列。分区表不支持部分索引。
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 部分索引的 `WHERE` 子句支持基本比较运算符(`=``!=``<``<=``>``>=`)、`IS NULL``IS NOT NULL` 和包含常量值的 `IN` 谓词。
- 谓词中的列和常量值必须具有相同的数据类型。
- 谓词只能引用同一表中的列。
- 不能在表达式索引上创建部分索引。
- 部分索引的 `WHERE` 子句仅支持单个简单谓词:基本比较运算符(`=``!=``<``<=``>``>=`)、`IS NULL``IS NOT NULL`。不支持 `IN`,也不支持使用 `AND``OR` 组合多个条件。
- 比较运算符两侧的列和字面量必须具有兼容的数据类型,不要求类型完全相同。
- 谓词只能引用同一表中的可见、非生成列。分区表不支持部分索引。

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

Labels

missing-translation-status This PR does not have translation status info. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant