pingcap / pingcap/tidb

ANALYZE TABLE (v2) panics "index out of range [-1]" when an index references a column absent from the analyzed column set — incl. clustered PK on a plain table

Open
#70,063 3 comments 0 reactions 0 assignees View on GitHub
contribution first-time-contributor may-affects-7.5 may-affects-8.1 may-affects-8.5 severity/major sig/planner type/bug
Dominant language
Go
Stars
40.5k
Forks
6.2k
PR merge metrics
PR metrics pending

Description

## Bug Report

Please answer these questions before submitting your issue. Thanks!

### 1. Minimal reproduce step (Required)
Repro A — index on generated columns (filtered out of the analyzed set):
```
CREATE TABLE t_gen (
col_pk varchar(100) NOT NULL,
payload json NOT NULL,
gen_a varchar(36) GENERATED ALWAYS AS (json_unquote(json_extract(payload,'$.a'))) STORED,
gen_b bigint GENERATED ALWAYS AS (cast(json_unquote(json_extract(payload,'$.b')) as unsigned)) STORED,
KEY idx_gen (gen_a, gen_b),
PRIMARY KEY (col_pk) /*T![clustered_index] CLUSTERED */
);
SET SESSION tidb_analyze_version = 2;
ANALYZE TABLE t_gen; -- panics: index out of range [-1]
```
filterSkipColumnTypes drops gen_a/gen_b (JSON-derived) from execColsInfo, but idx_gen still references them.

### 2. What did you expect to see? (Required)
ANALYZE completes and collects statistics.

### 3. What did you see instead (Required)
`ERROR 1105 (HY000): runtime error: index out of range [-1]`

The analyze worker panics (server logs analyze worker panicked); auto-analyze then retries the same table and re-panics, causing a crash loop.

```
executor.(*AnalyzeExec).analyzeWorker.func1 analyze.go:500 (recover)
runtime.goPanicIndex panic.go:115
executor.(*AnalyzeColumnsExecV2).analyzeColumnsPushDownV2 analyze_col_v2.go:79 <-- panic
executor.analyzeColumnsPushDownEntry analyze_col.go:69
executor.(*AnalyzeExec).analyzeWorker analyze.go:527
executor.(*AnalyzeExec).Next.func1 analyze.go:124
```
tidb_analyze_column_options = 'ALL' does not reliably avoid it: allColumns is a length-equality check (len(TableInfo.Columns) == len(execColsInfo)), and filterSkipColumnTypes can still trim columns.

### 4. What is your TiDB version? (Required)
8.5.5

### 5 RCA
Crash site — pkg/executor/analyze_col_v2.go, analyzeColumnsPushDownV2:
```
for i, idx := range e.indexes {
for _, col := range idx.Columns {
colInfo := e.colsInfo[col.Offset] // col.Offset == -1 -> panic
col.Offset becomes -1 upstream in pkg/planner/core/planbuilder.go:
```
`filterSkipColumnTypes` drops the generated columns gen_col_a / gen_col_b (they depend on the json column payload) from the analyzed set execColsInfo.
allColumns := len(tbl.TableInfo.Columns) == len(execColsInfo) ` → false (columns were filtered).
In getModifiedIndexesInfoForAnalyze, the non-allColumns branch remaps offsets: idx.Columns[i].Offset = getColOffsetForAnalyze(execColsInfo, colID), and getColOffsetForAnalyze returns -1 when the column isn't in execColsInfo. idx_gen references the filtered generated columns → -1 → consumed unguarded at analyze_col_v2.go:79.
Notes:

Columns are STORED (IsVirtualGenerated() == false), so this is not #61606 / PR #62333 (first-column virtual column, decode path). That PR does not touch analyze_col_v2.go:79 or the offset remap, so it does not fix this.
Column position is irrelevant; trigger is "a secondary index references a generated column filtered out of the analyzed set."
tidb_analyze_column_options = 'ALL' does not avoid it: allColumns is a length-equality check and filterSkipColumnTypes still trims the generated columns. Reproduces under both PREDICATE and ALL.

### 6 suggest fix
In getModifiedIndexesInfoForAnalyze (planbuilder.go), when getColOffsetForAnalyze returns -1 for any index column, do not assign a -1 offset — route that index to the independent/special (NDV-only) handling instead of the sampling path. Optionally add a defensive col.Offset < 0 guard at the consumers in analyze_col_v2.go (analyzeColumnsPushDownV2 and the subBuildWorker index sample-collect loop).

### 7 Workaround
`
SET SESSION tidb_analyze_version = 1;
ANALYZE TABLE sample_table;
`

Contributor guide

Open the contributing guide

Research direction

Reproduce the failure with the provided generated-column schema and ANALYZE commands, then read getModifiedIndexesInfoForAnalyze in pkg/planner/core/planbuilder.go and analyzeColumnsPushDownV2 in pkg/executor/analyze_col_v2.go. Trace how filtered generated columns produce an offset of -1; done means ANALYZE completes without a panic for both PREDICATE and ALL options.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.