cockroachdb / cockroachdb/cockroach
opt: delete cascade fast path can be sub-optimal
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
We have a special fast path for cascading delete queries which "transfers" filters from the parent to the child table instead of joining against buffered rows from the parent. One goal of this fast path is to use efficient delete-range requests instead of a join + delete operator. However, this is not always optimal, particularly when the foreign key includes multiple columns. This is relevant for multi-region clusters, since the `crdb_region` column is often included in foreign-key relations and often not explicitly specified in query filters.
The following example shows a case where the delete cascade fast path would prevent usage of an index on the child table:
Setup:
```
CREATE TABLE parent (a INT, b INT, PRIMARY KEY (a, b));
CREATE TABLE child (
ref_a INT NOT NULL,
ref_b INT NOT NULL,
FOREIGN KEY (ref_a, ref_b) REFERENCES parent (a, b) ON DELETE CASCADE,
INDEX idx (ref_b, ref_a)
);
```
Query:
```
DELETE FROM parent WHERE a = 1;
```
With fast-path cascade:
```
...
└── cascade
└── delete child
├── columns:
├── fetch columns: ref_a:14 ref_b:15 rowid:16
├── cardinality: [0 - 0]
├── volatile, mutations
└── select
├── columns: ref_a:14!null ref_b:15!null rowid:16!null
├── key: (16)
├── fd: ()-->(14), (16)-->(15)
├── scan child
│ ├── columns: ref_a:14!null ref_b:15!null rowid:16!null
│ ├── flags: avoid-full-scan
│ ├── key: (16)
│ └── fd: (16)-->(14,15)
└── filters
└── ref_a:14 = 1 [outer=(14), constraints=(/14: [/1 - /1]; tight), fd=()-->(14)]
```
Notice that there is only one filter constraining `ref_a`. Since `ref_b` is the leading column in the index, the filter cannot be used to perform a constrained scan, and the cascade may be slower than necessary.
Disabled fast-path cascade:
```
...
└── cascade
└── delete child
├── columns:
├── fetch columns: ref_a:14 ref_b:15 rowid:16
├── cardinality: [0 - 0]
├── volatile, mutations
└── project
├── columns: ref_a:14!null ref_b:15!null rowid:16!null
├── key: (16)
├── fd: (16)-->(14,15)
└── inner-join (lookup child@idx)
├── columns: ref_a:14!null ref_b:15!null rowid:16!null a:19!null b:20!null
├── key columns: [20 19] = [15 14]
├── key: (16)
├── fd: ()-->(14,19), (16)-->(15), (14)==(19), (19)==(14), (15)==(20), (20)==(15)
├── with-scan &1
│ ├── columns: a:19!null b:20!null
│ ├── mapping:
│ │ ├── parent.a:5 => a:19
│ │ └── parent.b:6 => b:20
│ ├── key: (20)
│ └── fd: ()-->(19)
└── filters (true)
```
Here, the optimizer is able to constrain the child table scan using columns fetched from the parent, allowing a lookup-join into the child.
Jira issue: CRDB-50667
Contributor guide
Assessment
This issue has not been assessed yet.