TiDB does not simplify `OR FALSE` in a `LEFT JOIN` query with an `IN` subquery, causing a large performance regression
- 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!
The following two queries are logically equivalent because `expr OR FALSE` should be simplified to `expr`.
However, TiDB v8.5.7 generates very different execution plans. The original query is optimized into inner/semi joins with null-rejecting predicates, while the `OR FALSE` query keeps a `Selection` with `or(Column#..., 0)` above a left outer semi join and a left outer join.
In my local reproduction, both queries return the same result, but the `OR FALSE` query is about 66x slower.
Observed timing over 8 alternating executions:
```text
original avg = 25.385 ms, median = 24.989 ms
mutated avg = 1668.338 ms, median = 1656.939 ms
median ratio = 66.31x slower
```
### 1. Minimal reproduce step (Required)
```sql
DROP DATABASE IF EXISTS tidb_or_false_min;
CREATE DATABASE tidb_or_false_min;
USE tidb_or_false_min;
CREATE TABLE a(id INT);
CREATE TABLE b(id INT, v INT);
CREATE TABLE d(n INT);
INSERT INTO d VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
INSERT INTO a
SELECT x.n * 1000 + y.n * 100 + z.n * 10 + w.n
FROM d AS x
JOIN d AS y
JOIN d AS z
JOIN d AS w;
INSERT INTO b
SELECT x.n * 10 + y.n, x.n * 10 + y.n
FROM d AS x
JOIN d AS y;
SELECT COUNT(*)
FROM a
LEFT JOIN b ON b.id NOT BETWEEN 13 AND 19
WHERE b.v IN (SELECT v FROM b WHERE v <> 68);
SELECT COUNT(*)
FROM a
LEFT JOIN b ON b.id NOT BETWEEN 13 AND 19
WHERE (b.v IN (SELECT v FROM b WHERE v <> 68)) OR FALSE;
```
Expected result for both queries:
```text
+----------+
| COUNT(*) |
+----------+
| 920000 |
+----------+
```
To inspect the plan difference:
```sql
EXPLAIN
SELECT COUNT(*)
FROM a
LEFT JOIN b ON b.id NOT BETWEEN 13 AND 19
WHERE b.v IN (SELECT v FROM b WHERE v <> 68);
EXPLAIN
SELECT COUNT(*)
FROM a
LEFT JOIN b ON b.id NOT BETWEEN 13 AND 19
WHERE (b.v IN (SELECT v FROM b WHERE v <> 68)) OR FALSE;
```
### 2. What did you expect to see? (Required)
TiDB should simplify `predicate OR FALSE` to `predicate` before optimization, or otherwise apply equivalent null-rejection and join simplification rules. The two semantically equivalent queries should have similar execution plans and similar execution time.
### 3. What did you see instead (Required)
Original query:
```text
HashAgg_19 funcs:count(1)->Column#9
HashJoin_22 CARTESIAN inner join
HashJoin_23(Build) inner join, equal:[eq(b.v, b.v)]
HashAgg_33(Build) group by:b.v
TableReader_34 data:HashAgg_28
HashAgg_28 cop[tikv] group by:b.v
Selection_32 ne(b.v, 68), not(isnull(b.v))
TableFullScan_31 table:b
TableReader_27(Probe) data:Selection_26
Selection_26 not(isnull(b.v)), or(lt(b.id,13), gt(b.id,19))
TableFullScan_25 table:b
TableReader_39(Probe) data:TableFullScan_38
TableFullScan_38 table:a
```
Query with `OR FALSE`:
```text
HashAgg_12 funcs:count(1)->Column#10
Selection_14 or(Column#9, 0)
HashJoin_15 CARTESIAN left outer semi join, other cond:eq(b.v,b.v)
TableReader_25(Build) data:Selection_24
Selection_24 ne(b.v,68)
TableFullScan_23 table:b
HashJoin_16(Probe) CARTESIAN left outer join
TableReader_22(Build) data:Selection_21
Selection_21 or(lt(b.id,13), gt(b.id,19))
TableFullScan_20 table:b
TableReader_19(Probe) data:TableFullScan_18
TableFullScan_18 table:a
```
### 4. What is your TiDB version? (Required)
```text
Release Version: v8.5.7
Edition: Community
Git Commit Hash: 202b7f47286a1109b5c957401d34c9358d130ae0
Git Branch: HEAD
UTC Build Time: 2026-07-15 02:06:00
GoVersion: go1.25.10
Race Enabled: false
Check Table Before Drop: false
Store: unistore
```
Contributor guide
Research direction
Run the supplied SQL reproduction and both EXPLAIN statements first, then trace TiDB's optimizer handling of the IN subquery predicate when it is wrapped in OR FALSE. Done means the equivalent queries receive equivalent simplification and join treatment, with a regression test covering the plan or behavior and avoiding the reported performance gap.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100