pingcap / pingcap/tidb

RECOVER TABLE table_name N is parsed but ignored, recovering a table outside the requested DDL-job selection

Open
#70,985 2 comments 0 reactions 0 assignees View on GitHub
affects-25.10 affects-26.3 affects-7.5 affects-8.1 affects-8.5 affects-9.0 component/ddl found-by-ai severity/major sig/sql-infra 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)

Run on a real-TiKV playground pinned to source `a514a92784c`:

```sql
CREATE DATABASE repro_recover_jobnum_main;
USE repro_recover_jobnum_main;
CREATE TABLE t(a INT);
INSERT INTO t VALUES (5);
DROP TABLE t;
CREATE TABLE marker(x INT);

-- The latest DDL job is now CREATE TABLE marker; the DROP TABLE t job is older.
SELECT JOB_ID, TABLE_NAME, JOB_TYPE
FROM information_schema.ddl_jobs
WHERE DB_NAME = 'repro_recover_jobnum_main'
ORDER BY JOB_ID DESC LIMIT 3;

-- A one-job window contains only CREATE TABLE marker, so this must not recover t.
RECOVER TABLE t 1;

SELECT a FROM t; -- actual: 5
```

Probe output:

```text
RECENT_JOB=171|marker|create table
RECENT_JOB=169|t|drop table
RECENT_JOB=168|t|create table
MAIN_LATEST_JOB_IS_CREATE_MARKER
MAIN_DROP_T_JOB_PRESENT=True
RECOVERED_ROWS=['5']
TIDB_JOB_NUMBER_IGNORED
```

Control in a separate database:

```text
CONTROL_ROWS=['7']
CONTROL_UNBOUNDED_RECOVER_OK
```

The unbounded `RECOVER TABLE t` control proves the recovery path and GC safe point are healthy. The RED is specific to the parsed integer being ignored.

### 2. What did you expect to see? (Required)

Pass `JobNum` into `getRecoverTableByTableName` and limit the history search to the most recent `JobNum` DDL jobs (or implement the intended job-id selection). If the syntax is intentionally unsupported, reject `RECOVER TABLE table_name N` in the parser/planner before executing. Add a regression test where the latest DDL job is a non-matching `CREATE TABLE` and `RECOVER TABLE t 1` must not recover the older dropped table.

### 3. What did you see instead (Required)

Fixture: CREATE TABLE t(a INT); INSERT INTO t VALUES(5); DROP TABLE t; CREATE TABLE marker(x INT). Recent jobs before recovery: CREATE TABLE marker (latest), DROP TABLE t (older). RECOVER TABLE t 1 returns success; information_schema.tables shows t; SELECT a FROM t returns 5. Control: unbounded RECOVER TABLE t recovers the separate control row 7.

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

```text
Release Version: v8.4.0-this-is-a-placeholder
Edition: Community
Git Commit Hash: None
Git Branch: None
UTC Build Time: None
GoVersion: go1.25.12
Race Enabled: false
Check Table Before Drop: false
Store: unistore
Kernel Type: Classic
```

Built from source commit `a514a92784c9654502686e6ee6efc9e0aeda8afa` (pingcap/tidb master, 2026-09-07).

### 5. Root cause (optional)

- pkg/parser/parser.y:3382-3394 RECOVER TABLE TableName Int64Num sets JobNum
- pkg/parser/ast/ddl.go:5653-5659 RecoverTableStmt.JobID, Table, JobNum
- pkg/executor/ddl.go:438-460 executeRecoverTable ignores s.JobNum and branches only on s.Table
- pkg/executor/ddl.go:535-592 getRecoverTableByTableName searches all history DDL jobs via ddl.IterHistoryDDLJobs and never reads JobNum
- TiDB v3.0.0 executor/ddl.go TODO: only search recent `e.JobNum` DDL jobs

Contributor guide

Open the contributing guide

Research direction

Run the SQL reproduction first, then read pkg/executor/ddl.go at executeRecoverTable and getRecoverTableByTableName, followed by the parser and AST definitions in pkg/parser/parser.y and pkg/parser/ast/ddl.go. Add the regression test requested in the issue so RECOVER TABLE t 1 does not recover an older dropped table when the latest DDL job is unrelated, while unbounded recovery still works.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.