pingcap / pingcap/tidb

planner: binary literals and prepared parameters can return extra rows with a case-insensitive composite index

Open
#69,852 0 comments 0 reactions 0 assignees View on GitHub
affects-7.5 affects-8.5 component/charset impact/wrong-result may-affects-8.1 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)

A raw binary literal or binary prepared parameter can make TiDB return rows that fail an explicit binary equality predicate. `FORCE INDEX` is used only to make the access path deterministic.

```sql
DROP TABLE IF EXISTS t;
CREATE TABLE t (
a BIGINT PRIMARY KEY,
b BIGINT NOT NULL,
c VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
KEY idx_bc (b, c)
);

INSERT INTO t VALUES
(1, 42, 'Alice'),
(2, 42, 'alice');

-- Non-prepared binary literal.
SELECT a, c
FROM t FORCE INDEX (idx_bc)
WHERE b = 42
AND c = _binary'Alice'
AND CAST(c AS BINARY) = CAST(_binary'Alice' AS BINARY)
ORDER BY a;
```

The same wrong result occurs with `X'416C696365'`, `0x416C696365`, and a binary prepared parameter:

Prepared parameter variant

```sql
SET @p1 = 42;
SET @p2 = CAST(_utf8mb4'Alice' AS BINARY);
SELECT CHARSET(@p2), COLLATION(@p2); -- binary, binary

PREPARE stmt FROM '
SELECT a, c
FROM t FORCE INDEX (idx_bc)
WHERE b = ?
AND c = ?
AND CAST(c AS BINARY) = CAST(? AS BINARY)
ORDER BY a';

EXECUTE stmt USING @p1, @p2, @p2;
```

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

Only the byte-exact match should be returned:

```text
a c
1 Alice
```

MySQL 8.4.10 returns only this row for `_binary`, `X'...'`, `0x...`, and binary prepared parameters. Its plan retains the binary filter above the case-insensitive index lookup:

```text
Filter: (cast(t.c as char charset binary) = 'Alice')
-> Covering index lookup on t using idx_bc (b=42, c=_binary'Alice')
```

The prepared case was also verified through the native protocol with the value bound as `MYSQL_TYPE_BLOB`.

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

TiDB returns both rows for these binary value forms:

| Value form | Execution | Returned IDs |
| --- | --- | --- |
| `_binary'Alice'` | direct SQL | `1,2` |
| `X'416C696365'` | direct SQL | `1,2` |
| `0x416C696365` | direct SQL | `1,2` |
| binary parameter | prepared statement | `1,2` |

The plan builds a range on `(b, c)`, but contains no residual `Selection` for the binary predicate:

```text
IndexReader
└─Projection
└─IndexRangeScan index:idx_bc(b, c)
range:[42 "\\x00A\\x00L\\x00I\\x00C\\x00E",
42 "\\x00A\\x00L\\x00I\\x00C\\x00E"]
```

As controls, direct `CAST('Alice' AS BINARY)`, `BINARY 'Alice'`, and a binary user variable return only row `1` and retain a `Selection`. Disabling `tidb_enable_prepared_plan_cache` does not change the prepared result.

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

Reproduced on TiDB v8.5.7:

```text
Release Version: v8.5.7
Edition: Community
Git Commit Hash: 202b7f47286a1109b5c957401d34c9358d130ae0
Git Branch: HEAD
UTC Build Time: 2026-07-07 08:10:56
GoVersion: go1.25.10
Race Enabled: false
Check Table Before Drop: false
Store: tikv
```

Also reproduced on:

- v7.5.7
- nightly `v9.0.0-beta.2.pre-1982-g2964713e26`

### Additional context

The fixes in [#67899](https://github.com/pingcap/tidb/issues/67899), [#67898](https://github.com/pingcap/tidb/pull/67898), and [#68498](https://github.com/pingcap/tidb/pull/68498) preserve residual filters for binary-cast literals, but do not cover raw binary literals or binary prepared parameters in this query shape.

Contributor guide

Open the contributing guide

Research direction

Start by running the SQL reproduction, including the direct and prepared binary-value variants, and inspect the planner output for IndexReader and IndexRangeScan on idx_bc. Trace why the binary equality predicate is not retained as a residual Selection; done means every binary form returns only the byte-exact Alice row while case-insensitive index lookup remains usable.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.