Incorrect MIN/MAX Result Due to TiKV TopN Push-down with CAST / BINARY Expression
- 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)
case 1
~~~sql
DROP DATABASE IF EXISTS repro39;
CREATE DATABASE repro39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
USE repro39;
CREATE TABLE src (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 CHAR(1)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO src(c0) VALUES ('-'), ('(');
CREATE TABLE l (
id BIGINT NOT NULL PRIMARY KEY,
c0 CHAR(1)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE r (
id BIGINT NOT NULL PRIMARY KEY,
c0 CHAR(1)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO l SELECT id, c0 FROM src;
INSERT INTO r SELECT id, c0 FROM src;
SELECT HEX(MIN(CAST(c0 AS BINARY))) AS source_hex,
MIN(CAST(c0 AS BINARY)) AS source_val
FROM src;
SELECT HEX(MIN(CAST(l.c0 AS BINARY))) AS join_hex,
MIN(CAST(l.c0 AS BINARY)) AS join_val
FROM l JOIN r ON l.id = r.id;
~~~
case 2
~~~sql
DROP DATABASE IF EXISTS repro41;
CREATE DATABASE repro41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
USE repro41;
CREATE TABLE src (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 CHAR(1)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO src(c0) VALUES (''), ('璘');
CREATE TABLE l (
id BIGINT NOT NULL PRIMARY KEY
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE r (
id BIGINT NOT NULL PRIMARY KEY,
c0 CHAR(1)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO l SELECT id FROM src;
INSERT INTO r SELECT id, c0 FROM src;
SELECT HEX(MAX(BINARY c0)) AS single_hex,
MAX(BINARY c0) AS single_val
FROM src;
SELECT HEX(MAX(BINARY r.c0)) AS join_hex,
MAX(BINARY r.c0) AS join_val
FROM l JOIN r ON l.id = r.id;
~~~
### 2. What did you expect to see? (Required)
**Case 1:** The binary minimum of ( (0x28) and - (0x2D) is ( (0x28).
Both single-table and join queries should return 0x28 and the character (.
**Case 2:** The binary maximum of (0xEEAC86) and 璘 (0xEFA7AF) is 璘 (0xEFA7AF).
Both single-table and join queries should return 0xEFA7AF and the character 璘.
### 3. What did you see instead (Required)
Single-table query returns incorrect result:
Case 1: source_hex = 2D, source_val = - (should be ()
Case 2: single_hex = EEAC86, single_val = (should be 璘)
Join query returns correct result as expected in both cases.
### 4. What is your TiDB version? (Required)
I tested such a case in TiDB-v8.5.6. and TiDB-v9.0.0. Maybe it is an issue that exists in all the versions.
### 5. Execution Plan Differences
**Single-table query (wrong)** – TopN is pushed down to TiKV (cop[tikv]):
~~~sql
TopN_20 [cop[tikv]] cast(repro39.src.c0, var_string(1) BINARY), offset:0, count:1
~~~
**Join query (correct)** – TopN stays at TiDB root:
~~~sql
TopN_20 [root] Column#10, offset:0, count:1
~~~
### 6. Root Cause
TiDB optimizes MIN(expr) and MAX(expr) into TopN(expr, [asc|desc], 1). When expr includes a type cast to binary (e.g., CAST(c0 AS BINARY) or BINARY c0), and the TopN operator is pushed down to TiKV, TiKV’s expression evaluation engine produces an incorrect sort order, resulting in wrong aggregate results.
The bug is consistently reproducible in single-table queries. In multi-table JOIN queries, the TopN remains at the TiDB root level, avoiding the faulty TiKV evaluation and yielding correct results.
Contributor guide
Assessment
This issue has not been assessed yet.