pingcap / pingcap/tidb

Slice bounds out of range panic in cascades planner when generating plan for indexed table

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

Description

## Bug Report

### 1. Minimal reproduce step (Required)

Save the following SQL to a file (e.g., `repro.sql`) and execute it against a fresh TiDB instance:

```sql
USE test;
DROP DATABASE IF EXISTS database20;
CREATE DATABASE database20;
USE database20;
CREATE TABLE IF NOT EXISTS idx_t2 (c0 INT NOT NULL, c1 FLOAT NOT NULL, c2 VARCHAR(64), c3 INT NOT NULL, c4 FLOAT, c5 VARCHAR(64) NOT NULL, c6 INT, c7 FLOAT NOT NULL);
CREATE INDEX IF NOT EXISTS idx_2_c0 ON idx_t2 (c0);
CREATE INDEX IF NOT EXISTS idx_2_c1 ON idx_t2 (c1);
CREATE INDEX IF NOT EXISTS idx_2_c0c1 ON idx_t2 (c0, c1);
CREATE INDEX IF NOT EXISTS idx_2_c5 ON idx_t2 (c5);
CREATE INDEX IF NOT EXISTS idx_2_c1desc ON idx_t2 (c1 DESC);
CREATE INDEX IF NOT EXISTS idx_2_c4 ON idx_t2 (c4);
CREATE INDEX IF NOT EXISTS idx_2_c7 ON idx_t2 (c7);
INSERT IGNORE INTO idx_t2(c0, c1, c2, c3, c4, c5, c6, c7) VALUES (43, 352.9813385747763, '-406092536', 20, 213.9958336986293, 'mG', 57, 1.108112066E12);
CREATE INDEX cover_idx_3120 ON idx_t2 (c3, c4, c6);
SET SESSION tidb_opt_enable_hash_join=0;
CREATE UNIQUE INDEX uniq_idx_3124 ON idx_t2 (c7);
CREATE INDEX range_idx_3118 ON idx_t2 (c1);
CREATE UNIQUE INDEX uniq_idx_3117 ON idx_t2 (c0);
CREATE INDEX expr_idx_9610 ON idx_t2 ((LOWER(c2)));
CREATE INDEX imerge_a_3117 ON idx_t2 (c0);
CREATE INDEX imerge_b_3118 ON idx_t2 (c1);
CREATE INDEX imerge_a_3117 ON idx_t2 (c0);
CREATE INDEX imerge_b_3123 ON idx_t2 (c6);
DROP INDEX idx_2_c0 ON idx_t2;
ANALYZE TABLE idx_t2;
CREATE INDEX imerge_a_3118 ON idx_t2 (c1);
CREATE INDEX imerge_b_3124 ON idx_t2 (c7);
CREATE INDEX imerge_a_3118 ON idx_t2 (c1);
CREATE INDEX imerge_b_3120 ON idx_t2 (c3);
CREATE INDEX range_idx_3124 ON idx_t2 (c7);
CREATE INDEX ordlim_idx_3124 ON idx_t2 (c7);
CREATE INDEX cover_idx_3121 ON idx_t2 (c4, c1);
CREATE UNIQUE INDEX uniq_idx_3123 ON idx_t2 (c6);
SET SESSION tidb_opt_insubq_to_join_and_agg=0;
SET SESSION tidb_opt_agg_push_down=1;
CREATE INDEX sq_in_3123 ON idx_t2 (c6);
SET SESSION tidb_enable_inl_join=0;
CREATE INDEX ordlim_idx_3121 ON idx_t2 (c4);
SET SESSION tidb_enable_index_fast_plan=0;
CREATE INDEX range_idx_3118 ON idx_t2 (c1);
CREATE INDEX prefix_idx_3122 ON idx_t2 (c5(1));
CREATE UNIQUE INDEX uniq_idx_3124 ON idx_t2 (c7);
CREATE INDEX range_idx_3120 ON idx_t2 (c3);
ANALYZE TABLE idx_t2;
CREATE INDEX imerge_a_3124 ON idx_t2 (c7);
CREATE INDEX imerge_b_3120 ON idx_t2 (c3);
DROP INDEX idx_2_c1 ON idx_t2;
CREATE INDEX cover_idx_3120 ON idx_t2 (c3, c7);
CREATE INDEX imerge_a_3124 ON idx_t2 (c7);
CREATE INDEX imerge_b_3121 ON idx_t2 (c4);
CREATE INDEX cover_idx_3123 ON idx_t2 (c6, c0, c1);
CREATE INDEX range_idx_3120 ON idx_t2 (c3);
SET SESSION tidb_enable_index_fast_plan=0;
CREATE INDEX cover_idx_3118 ON idx_t2 (c1, c3, c4);
CREATE INDEX sq_out_3121 ON idx_t2 (c4);
CREATE INDEX ordlim_idx_3124 ON idx_t2 (c7);
CREATE INDEX ordlim_idx_3121 ON idx_t2 (c4);
CREATE UNIQUE INDEX uniq_idx_3118 ON idx_t2 (c1);
CREATE INDEX ordlim_idx_3117 ON idx_t2 (c0);
ANALYZE TABLE idx_t2;
CREATE INDEX ordlim_idx_3120 ON idx_t2 (c3);
CREATE INDEX loose_idx_3123 ON idx_t2 (c6, c7);
CREATE INDEX prefix_idx_3119 ON idx_t2 (c2(2));
CREATE INDEX imerge_a_3124 ON idx_t2 (c7);
CREATE INDEX imerge_b_3120 ON idx_t2 (c3);
CREATE INDEX range_idx_3123 ON idx_t2 (c6);
CREATE INDEX expr_idx_8962 ON idx_t2 ((ABS(c6)));
CREATE INDEX imerge_a_3118 ON idx_t2 (c1);
CREATE INDEX imerge_b_3120 ON idx_t2 (c3);
CREATE INDEX prefix_idx_3119 ON idx_t2 (c2(10));
CREATE INDEX range_idx_3121 ON idx_t2 (c4);
CREATE INDEX null_idx_3123 ON idx_t2 (c6);
CREATE INDEX prefix_idx_3122 ON idx_t2 (c5(2));
ANALYZE TABLE idx_t2;
SET SESSION tidb_enable_ordered_index_mode=0;
CREATE INDEX prefix_idx_3119 ON idx_t2 (c2(2));
CREATE INDEX imerge_a_3117 ON idx_t2 (c0);
CREATE INDEX imerge_b_3121 ON idx_t2 (c4);
SET SESSION tidb_enable_index_merge=0;
CREATE INDEX imerge_a_3117 ON idx_t2 (c0);
CREATE INDEX imerge_b_3121 ON idx_t2 (c4);
SET SESSION tidb_opt_prefer_range_scan=1;
CREATE INDEX null_idx_3121 ON idx_t2 (c4);
CREATE INDEX loose_idx_3118 ON idx_t2 (c1, c7);
CREATE INDEX imerge_a_3124 ON idx_t2 (c7);
CREATE INDEX imerge_b_3118 ON idx_t2 (c1);
CREATE INDEX sq_out_3124 ON idx_t2 (c7);
SET SESSION tidb_opt_prefer_range_scan=1;
CREATE INDEX prefix_idx_3122 ON idx_t2 (c5(2));
CREATE INDEX ordlim_idx_3118 ON idx_t2 (c1);
CREATE UNIQUE INDEX uniq_idx_3117 ON idx_t2 (c0);
CREATE INDEX loose_idx_3117 ON idx_t2 (c0, c7);
CREATE INDEX range_idx_3118 ON idx_t2 (c1);
CREATE INDEX ordlim_idx_3118 ON idx_t2 (c1);
CREATE UNIQUE INDEX uniq_idx_3124 ON idx_t2 (c7);
CREATE INDEX sq_in_3117 ON idx_t2 (c0);
ANALYZE TABLE idx_t2;
CREATE INDEX null_idx_3123 ON idx_t2 (c6);
CREATE INDEX expr_idx_8947 ON idx_t2 ((c6 + 0));
CREATE INDEX sq_out_3123 ON idx_t2 (c6);
SET SESSION tidb_enable_inl_join=0;
CREATE INDEX expr_idx_9222 ON idx_t2 ((UPPER(c2)));
DROP INDEX idx_2_c0c1 ON idx_t2;
CREATE INDEX imerge_a_3123 ON idx_t2 (c6);
CREATE INDEX imerge_b_3118 ON idx_t2 (c1);
CREATE INDEX imerge_a_3118 ON idx_t2 (c1);
CREATE INDEX imerge_b_3120 ON idx_t2 (c3);
CREATE INDEX null_idx_3123 ON idx_t2 (c6);
CREATE INDEX range_idx_3124 ON idx_t2 (c7);
CREATE INDEX sq_in_3121 ON idx_t2 (c4);
SET SESSION tidb_opt_agg_push_down=1;
CREATE INDEX sq_out_3123 ON idx_t2 (c6);
ANALYZE TABLE idx_t2;
CREATE INDEX ordlim_idx_3121 ON idx_t2 (c4);
CREATE INDEX ordlim_idx_3120 ON idx_t2 (c3);
CREATE INDEX loose_idx_3117 ON idx_t2 (c0, c4);
CREATE INDEX null_idx_3123 ON idx_t2 (c6);
SET SESSION tidb_opt_enable_correlation_adjustment=0;
CREATE INDEX sq_out_3121 ON idx_t2 (c4);
SET SESSION tidb_opt_insubq_to_join_and_agg=0;
CREATE INDEX expr_idx_0 ON idx_t2 ((LOWER(c5)));
CREATE INDEX null_idx_3121 ON idx_t2 (c4);
CREATE INDEX sq_in_3118 ON idx_t2 (c1);
CREATE UNIQUE INDEX uniq_idx_3117 ON idx_t2 (c0);
ANALYZE TABLE idx_t2;
CREATE INDEX loose_idx_3117 ON idx_t2 (c0, c6);
CREATE UNIQUE INDEX uniq_idx_3118 ON idx_t2 (c1);
CREATE INDEX loose_idx_3124 ON idx_t2 (c7, c0);
CREATE INDEX sq_in_3124 ON idx_t2 (c7);
CREATE INDEX expr_idx_1389 ON idx_t2 ((UPPER(c2)));
SET SESSION tidb_enable_index_merge_join=0;
SET SESSION tidb_enable_cascades_planner=1;
SET SESSION tidb_enable_ordered_index_mode=0;
CREATE INDEX sq_in_3119 ON idx_t2 (c2);
CREATE UNIQUE INDEX uniq_idx_3118 ON idx_t2 (c1);
CREATE INDEX prefix_idx_3122 ON idx_t2 (c5(10));
ANALYZE TABLE idx_t2;
CREATE INDEX expr_idx_323 ON idx_t2 ((c6 + 0));
SELECT c3 FROM idx_t2 WHERE c3 IS NOT NULL AND c6 IS NOT NULL ORDER BY c3 LIMIT 70;
```

**Quick reproduce with Docker:**

```bash
docker run -d --name tidb-test -p 4000:4000 pingcap/tidb:v8.5.6 --store=unistore
sleep 15
mysql -h 127.0.0.1 -P 4000 -u root -f < repro.sql
```

The last line of output will show:
```
ERROR 1105 (HY000) at line 139: runtime error: slice bounds out of range [8:4]
```

### 2. What did you expect to see? (Required)
The SELECT query should execute successfully and return the following result:

```
+------+
| c3 |
+------+
| 20 |
+------+
1 row in set
```

The table has only 1 row where `c3 = 20` and `c6 = 57` (both NOT NULL), so the query should return that single row. TiDB should handle the query gracefully even with many indexes (including expression indexes) created on the table.

### 3. What did you see instead (Required)
TiDB panics with a Go runtime error: `slice bounds out of range [8:4]`, which is caught by TiDB's panic recovery mechanism and returned to the client as:

```
ERROR 1105 (HY000): runtime error: slice bounds out of range [8:4]
```

The TiDB server log shows:
```
runtime error: slice bounds out of range [8:4]
```

This error is **deterministic** and reproduces every time the script is executed.

**Key observations:**
- **The bug is triggered by the cascades planner.** Setting `SET SESSION tidb_enable_cascades_planner=1` is **required** to reproduce the bug. Without it, the same query returns the correct result normally. This confirms the bug is in the cascades planner code path.
- The bug requires a sequence of repeated `CREATE INDEX` + `ANALYZE TABLE` operations (including expression indexes like `(c6 + 0)`, `(LOWER(c2))`, `(UPPER(c2))`, `(LOWER(c5))`) to build up internal state
- The bug triggers on the final `SELECT` query with `ORDER BY c3 LIMIT 70`
- The slice bounds value varies depending on the number of rows/operations: `[8:4]` with 1 row, `[256:128]` with 50 rows
- Reducing the number of index operations below ~125 makes the bug disappear, suggesting an internal array/slice overflow in statistics or index handling

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

```
Release Version: v8.5.6
Edition: Community
Git Commit Hash: ae18096e023780bb56bfce33698abec0d4640d0a
Git Branch: HEAD
UTC Build Time: 2026-04-24 09:13:10
GoVersion: go1.25.8
Race Enabled: false
Check Table Before Drop: false
Store: unistore
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.