cockroachdb / cockroachdb/cockroach
sql: unnecessary lookup join in UPDATE with self-join
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
In order to use SKIP LOCKED within an UPDATE statement, it's necessary to use a self join. Depending on the columns used, CRDB isn't always able to eliminate this self join even when the initial scan could provide equivalent columns.
For example, consider this UPDATE statement:
```sql
CREATE TABLE abc (
a INT NOT NULL,
b INT NOT NULL,
c INT NOT NULL,
PRIMARY KEY (a),
INDEX (c, b)
);
EXPLAIN UPDATE abc SET b = b + 1 WHERE c = 5;
-- • update
-- │ table: abc
-- │ set: b
-- │ auto commit
-- │
-- └── • render
-- │
-- └── • scan
-- missing stats
-- table: abc@abc_c_b_idx
-- spans: [/5 - /5]
-- locking strength: for update
```
The initial scan of the secondary index provides all the columns we need to write to the table, so there isn't an index join or a lookup join to the primary index.
But if we add an equivalent self-join, there might be an unnecessary join to the primary index:
```sql
EXPLAIN UPDATE abc SET b = b + 1 WHERE a IN (SELECT a FROM abc WHERE c = 5 FOR UPDATE SKIP LOCKED);
-- • update
-- │ table: abc
-- │ set: b
-- │ auto commit
-- │
-- └── • render
-- │
-- └── • lookup join
-- │ estimated row count: 1
-- │ table: abc@abc_pkey
-- │ equality: (a) = (a)
-- │ equality cols are key
-- │
-- └── • scan
-- estimated row count: 1 (100% of the table; stats collected 2 minutes ago)
-- table: abc@abc_c_b_idx
-- spans: [/5 - /5]
-- locking strength: for update
-- locking wait policy: skip locked
```
If we rewrite the query to:
- be decorrelated (i.e. use the FROM syntax)
- include all columns from the self join (SELECT *)
- only reference columns from the self join in the SET part
then we can eliminate the unnecessary join:
```sql
EXPLAIN UPDATE abc AS y SET b = x.b + 1 FROM (SELECT * FROM abc WHERE c = 5 FOR UPDATE SKIP LOCKED) AS x WHERE y.a = x.a;
-- • update
-- │ table: abc
-- │ set: b
-- │ auto commit
-- │
-- └── • render
-- │
-- └── • scan
-- estimated row count: 1 (100% of the table; stats collected 16 minutes ago)
-- table: abc@abc_c_b_idx
-- spans: [/5 - /5]
-- locking strength: for update
-- locking wait policy: skip locked
```
It would be nice if the optimizer could do this automatically if the join uses the PK.
Jira issue: CRDB-53519
Contributor guide
Assessment
This issue has not been assessed yet.