cockroachdb / cockroachdb/cockroach

redundant join operation in the query plan of a normal SELECT

Open
#162,506 1 comment 0 reactions 1 assignee Claimed by @michae2 View on GitHub
C-bug O-community T-sql-queries X-blathers-triaged
Dominant language
Go
Stars
32.5k
Forks
4.1k
PR merge metrics
PR metrics pending

Description

**Describe the problem**

Please describe the issue you observed, and any steps we can take to reproduce it:

**To Reproduce**

Hi,

In the following test case, there are two equivalent queries, one is a normal SELECT, and the other is a prepared SELECT. However, I found that the normal SELECT generates a query plan that is less efficient than the one generated by the prepared SELECT. As the redundant merge join is applied in normal SELECT, which causes it to be slower than the prepared SELECT (more than 33x). Given that prepared statements typically result in suboptimal query plans, the query plan generation process for normal SELECT statements may still have room for further optimization.

```
CREATE TABLE t0 (c0 BYTES, CONSTRAINT "primary" PRIMARY KEY(c0 ASC), FAMILY "primary" (c0));
INSERT INTO t0 (c0) SELECT (gen_random_uuid()::TEXT)::BYTES FROM generate_series(1, 100000);
EXPLAIN ANALYZE SELECT CONCAT_AGG(((''::text))) FROM t0 NATURAL JOIN t0 AS t0_0 FULL OUTER JOIN t0 AS t0_1 ON IF(((true)AND(false)), (t0.c0) IN (t0_1.c0, t0_1.c0, t0.c0, t0.c0, t0_1.c0), ((t0.c0)IS DISTINCT FROM(t0.c0))) WHERE (t0.c0) BETWEEN SYMMETRIC (t0_1.c0) AND (TO_IP(''::text)) ORDER BY TIME '1969-12-12T20:00:15' DESC;
--------------------------------------------------------------------------------------------
planning time: 4ms
execution time: 3.3s
distribution: local
plan type: custom
rows decoded from KV: 200,000 (13 MiB, 2 gRPC calls)
cumulative time spent due to contention: 2.4s
cumulative time spent in the lock table: 2.3s
cumulative time spent waiting to acquire latches: 83ms

• group (scalar)
│ sql nodes: n1
│ execution time: 21µs
│ sql cpu time: 21µs
│ actual row count: 1

└── • render
│ execution time: 9µs
│ sql cpu time: 9µs
│ actual row count: 0

└── • cross join
│ sql nodes: n1
│ execution time: 20µs
│ sql cpu time: 20µs
│ actual row count: 0
│ pred: ((c0 >= c0) AND (c0 <= to_ip(''))) OR ((c0 >= to_ip('')) AND (c0 <= c0))

├── • scan
│ sql nodes: n1
│ KV time: 6µs
│ KV rows decoded: 0
│ sql cpu time: 6µs
│ actual row count: 0
│ missing stats
│ table: t0@primary
│ spans: FULL SCAN

└── • merge join
│ sql nodes: n1
│ execution time: 38µs
│ sql cpu time: 38µs
│ actual row count: 0
│ equality: (c0) = (c0)
│ left cols are key
│ right cols are key

├── • filter
│ │ sql nodes: n1
│ │ execution time: 19ms
│ │ sql cpu time: 19ms
│ │ actual row count: 0
│ │ filter: c0 IS DISTINCT FROM c0
│ │
│ └── • scan
│ sql nodes: n1
│ kv nodes: n1
│ KV time: 3.1s
│ KV contention time: 2.4s
│ KV lock wait time: 2.3s
│ KV latch wait time: 83ms
│ KV rows decoded: 100,000
│ sql cpu time: 25ms
│ actual row count: 100,000
│ missing stats
│ table: t0@primary
│ spans: FULL SCAN

└── • filter
│ sql nodes: n1
│ execution time: 17ms
│ sql cpu time: 17ms
│ actual row count: 0
│ filter: c0 IS DISTINCT FROM c0

└── • scan
sql nodes: n1
kv nodes: n1
KV time: 79ms
KV rows decoded: 100,000
sql cpu time: 22ms
actual row count: 100,000
missing stats
table: t0@primary
spans: FULL SCAN

PREPARE prepare_query (text, bool, bool, text) AS SELECT CONCAT_AGG((($1::string))) FROM t0 NATURAL JOIN t0 AS t0_0 FULL OUTER JOIN t0 AS t0_1 ON IF((($2)AND($3)), (t0.c0) IN (t0_1.c0, t0_1.c0, t0.c0, t0.c0, t0_1.c0), ((t0.c0)IS DISTINCT FROM(t0.c0))) WHERE (t0.c0) BETWEEN SYMMETRIC (t0_1.c0) AND (TO_IP($4::string)) ORDER BY TIME '1969-12-12T20:00:15' DESC;
EXPLAIN ANALYZE EXECUTE prepare_query(''::text, true, false, ''::text);
info
--------------------------------------------------------------------------------------------
planning time: 2ms
execution time: 91ms
distribution: local
plan type: custom
rows decoded from KV: 100,000 (6.4 MiB, 1 gRPC calls)

• group (scalar)
│ sql nodes: n1
│ execution time: 21µs
│ sql cpu time: 21µs
│ actual row count: 1

└── • render
│ execution time: 8µs
│ sql cpu time: 8µs
│ actual row count: 0

└── • cross join
│ sql nodes: n1
│ execution time: 24µs
│ sql cpu time: 24µs
│ actual row count: 0
│ pred: ((c0 >= c0) AND (c0 <= to_ip(''))) OR ((c0 >= to_ip('')) AND (c0 <= c0))

├── • scan
│ sql nodes: n1
│ KV time: 4µs
│ KV rows decoded: 0
│ sql cpu time: 4µs
│ actual row count: 0
│ missing stats
│ table: t0@primary
│ spans: FULL SCAN

└── • filter
│ sql nodes: n1
│ execution time: 16ms
│ sql cpu time: 16ms
│ actual row count: 0
│ filter: c0 IS DISTINCT FROM c0

└── • scan
sql nodes: n1
kv nodes: n1
KV time: 74ms
KV rows decoded: 100,000
sql cpu time: 22ms
actual row count: 100,000
missing stats
table: t0@primary
spans: FULL SCAN
```
**Expected behavior**
The normal SELECT should be faster or the same as the prepared SELECT.

**Additional data / screenshots**
If the problem is SQL-related, include a copy of the SQL query and the schema
of the supporting tables.

If a node in your cluster encountered a fatal error, supply the contents of the
log directories (at minimum of the affected node(s), but preferably all nodes).

Note that log files can contain confidential information. Please continue
creating this issue, but contact support@cockroachlabs.com to submit the log
files in private.

If applicable, add screenshots to help explain your problem.

**Environment:**
- CockroachDB version [CockroachDB CCL v26.2.0-alpha.00000000-dev-1ad60fb00289249b886bf98b08c8066e9475624e-dirty (x86_64-pc-linux-gnu, built 2026/02/03 03:00:41, go1.25.5)]
- Server OS: [Ubuntu 24.04]
- Client app [`cockroach sql`, JDBC]

**Additional context**
What was the impact?

Add any other context about the problem here.

Jira issue: CRDB-59505

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.