cockroachdb / cockroachdb/cockroach

sql: consider propagating locks through CTEs

Open
#144,105 0 comments 0 reactions 0 assignees View on GitHub
C-enhancement T-sql-queries
Dominant language
Go
Stars
32.5k
Forks
4.1k
PR merge metrics
PR metrics pending

Description

For-update locks are push into reads of `UPDATE` statements to reduce contention. For example, notice the for-update locks in the scan and index join below:

```sql
CREATE TABLE abc (
a INT PRIMARY KEY,
b INT,
c INT,
INDEX (b)
);

EXPLAIN (VERBOSE)
UPDATE abc SET c = c + 1 WHERE b = 10 RETURNING a;
-- info
-- ---------------------------------------------------------------------------------------------------------------------
-- distribution: local
-- vectorized: true
--
-- • update
-- │ columns: (a)
-- │ estimated row count: 10 (missing stats)
-- │ table: abc
-- │ set: c
-- │ auto commit
-- │
-- └── • render
-- │ columns: (a, b, c, c_new)
-- │ render c_new: c + 1
-- │ render a: a
-- │ render b: b
-- │ render c: c
-- │
-- └── • index join
-- │ columns: (a, b, c)
-- │ estimated row count: 10 (missing stats)
-- │ table: abc@abc_pkey
-- │ key columns: a
-- │ locking strength: for update
-- │
-- └── • scan
-- columns: (a, b)
-- estimated row count: 10 (missing stats)
-- table: abc@abc_b_idx
-- spans: /10-/11
-- locking strength: for update
```

If we rewrite the query to use a CTE, the scan of `abc_b_idx` and the lookup join no longer have for-update locks:

```sql
EXPLAIN (VERBOSE)
WITH l AS (
SELECT a FROM abc WHERE b = 10
), d AS (
UPDATE abc SET c = c + 1 WHERE a IN (SELECT a FROM l) RETURNING a
)
SELECT * FROM l;
-- info
-- -----------------------------------------------------------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │ columns: (a)
-- │
-- ├── • scan buffer
-- │ columns: (a)
-- │ estimated row count: 10 (missing stats)
-- │ label: buffer 1 (l)
-- │
-- ├── • subquery
-- │ │ id: @S1
-- │ │ original sql: SELECT a FROM abc WHERE b = 10
-- │ │ exec mode: discard all rows
-- │ │
-- │ └── • buffer
-- │ │ columns: (a)
-- │ │ label: buffer 1 (l)
-- │ │
-- │ └── • project
-- │ │ columns: (a)
-- │ │
-- │ └── • scan
-- │ columns: (a, b)
-- │ estimated row count: 10 (missing stats)
-- │ table: abc@abc_b_idx
-- │ spans: /10-/11
-- │
-- └── • subquery
-- │ id: @S2
-- │ original sql: UPDATE abc SET c = c + 1 WHERE a IN (SELECT a FROM l) RETURNING a
-- │ exec mode: discard all rows
-- │
-- └── • buffer
-- │ columns: (a)
-- │ label: buffer 2 (d)
-- │
-- └── • update
-- │ columns: (a)
-- │ estimated row count: 10 (missing stats)
-- │ table: abc
-- │ set: c
-- │
-- └── • render
-- │ columns: (a, b, c, c_new)
-- │ render c_new: c + 1
-- │ render a: a
-- │ render b: b
-- │ render c: c
-- │
-- └── • project
-- │ columns: (a, b, c)
-- │
-- └── • lookup join (inner)
-- │ columns: (a, a, b, c)
-- │ estimated row count: 10 (missing stats)
-- │ table: abc@abc_pkey
-- │ equality: (a) = (a)
-- │ equality cols are key
-- │
-- └── • scan buffer
-- columns: (a)
-- estimated row count: 10 (missing stats)
-- label: buffer 1 (l)
```

If a CTE is reference by an `UPDATE` statement, it would be nice if we pushed locks across the CTE boundary. Is this safe to do in all cases or only when the CTE is _only_ referenced by mutations?

Jira issue: CRDB-49230

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.