`ORDER BY`-Triggered Aggregation Push‑Down Causes TiKV to Pad `CAST(... AS TIME)` Result, Inflating `BIT_LENGTH()`
- 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)
```sql
DROP DATABASE IF EXISTS repro_tidb617_1_db18_min;
CREATE DATABASE repro_tidb617_1_db18_min;
USE repro_tidb617_1_db18_min;
SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
SET time_zone='+00:00';
CREATE TABLE src(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 DECIMAL(65,30) NULL
);
INSERT INTO src(c0) VALUES (0);
CREATE TABLE l(
id BIGINT NOT NULL PRIMARY KEY,
c0 DECIMAL(65,30) NULL
);
CREATE TABLE r(
id BIGINT NOT NULL PRIMARY KEY
);
INSERT INTO l SELECT id, c0 FROM src;
INSERT INTO r SELECT id FROM src;
-- Single‑table with ORDER BY: returns 80
SELECT MAX(
BIT_LENGTH(
CASE c0
WHEN ((BINARY id) | (id NOT LIKE c0)) THEN NULL
ELSE CAST(0.7772428249950747 AS TIME)
END
)
) AS single_val
FROM src
WHERE 1
ORDER BY id;
-- Join/rewrite: returns 64
SELECT MAX(
BIT_LENGTH(
CASE v.c0
WHEN ((BINARY v.id) | (v.id NOT LIKE v.c0)) THEN NULL
ELSE CAST(0.7772428249950747 AS TIME)
END
)
) AS split_val
FROM (
SELECT l.id, l.c0
FROM l JOIN r ON l.id = r.id
) v
WHERE 1
ORDER BY v.id;
```
### 2. What Did You Expect to See? (Required)
Row‑by‑row evaluation at the TiDB root layer shows that the `CASE` expression returns `'00:00:01'` (a string of length 8), and `BIT_LENGTH('00:00:01')` = 64. Therefore `MAX(BIT_LENGTH(...))` should be 64 for both the single‑table and the join query.
### 3. What Did You See Instead? (Required)
- Single‑table query with `ORDER BY id`: returns `80` (wrong)
- Join query: returns `64` (correct)
- Single‑table query without `ORDER BY id`: also returns `64` — the bug disappears when `ORDER BY` is removed.
### 4. What Is Your TiDB Version? (Required)
Version: TiDB‑v9.0.0
### 5. Execution Plan Differences
**Single‑table with `ORDER BY id` (wrong)** — aggregation pushed down to TiKV:
```
Sort_9 [root]
└─HashAgg_17 [root] funcs:max(Column#6)->Column#4, firstrow(Column#7)->src.id
└─TableReader_18
└─HashAgg_11 [cop[tikv]] funcs:max(bit_length(cast(case(..., NULL, 00:00:01), var_string(10))))->Column#6
└─TableFullScan_16 [cop[tikv]]
```
The `CAST(... AS TIME)` result is internally typed as `var_string(10)` inside TiKV, giving `BIT_LENGTH` = 80.
**Join query (correct)** — expression evaluated at TiDB root before aggregation:
```
Sort_14 [root]
└─HashAgg_18 [root] funcs:max(Column#8)->Column#6
└─Projection_63 [root] bit_length(cast(case(..., , 00:00:01), var_string(10)))->Column#8
└─MergeJoin_21 [root]
```
At the root layer the same `var_string(10)` cast yields a string of 8 characters, so `BIT_LENGTH` correctly returns 64.
### 6. Root Cause
The `ORDER BY id` clause in an aggregate query without `GROUP BY` forces the optimizer to preserve the non‑aggregated column `id`. This triggers a two‑phase aggregation strategy where partial `MAX(BIT_LENGTH(...))` and `firstrow(id)` are pushed down to TiKV.
When TiKV evaluates `CAST(0.7772428249950747 AS TIME)`, it stores the result as a `var_string(10)` — i.e., a fixed‑width 10‑byte string, padding the time value `'00:00:01'` to `'00:00:01 '` (or similar), thus `BIT_LENGTH` = 80. In contrast, TiDB's root expression evaluator treats the same cast as a normal variable‑length string with 8 characters, yielding `BIT_LENGTH` = 64.
Contributor guide
Assessment
This issue has not been assessed yet.