cockroachdb / cockroachdb/cockroach
opt: invisible index improvements
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
There are a few usability improvements for invisible indexes that would be nice to have:
## 1. Do not use an invisible index for FK constraint checks if there is a duplicate, non-visible index.
In the example below, the invisible `idx1` is used instead of the duplicate, visible `idx2`.
```sql
CREATE TABLE p (
k INT PRIMARY KEY
);
CREATE TABLE c (
k INT PRIMARY KEY,
p_k INT REFERENCES p(k),
INDEX idx1 (p_k) NOT VISIBLE,
INDEX idx2 (p_k)
);
EXPLAIN
DELETE FROM p WHERE k = 1;
-- info
-- ---------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • delete
-- │ │ from: p
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • scan
-- │ missing stats
-- │ table: p@p_pkey
-- │ spans: [/1 - /1]
-- │
-- └── • constraint-check
-- │
-- └── • error if rows
-- │
-- └── • lookup join (semi)
-- │ table: c@idx1
-- │ equality: (k) = (p_k)
-- │
-- └── • scan buffer
-- label: buffer 1
-- (26 rows)
EXPLAIN
UPDATE p SET k = 2 WHERE k = 1;
-- info
-- --------------------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • update
-- │ │ table: p
-- │ │ set: k
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • render
-- │ │
-- │ └── • scan
-- │ missing stats
-- │ table: p@p_pkey
-- │ spans: [/1 - /1]
-- │ locking strength: for update
-- │
-- └── • constraint-check
-- │
-- └── • error if rows
-- │
-- └── • lookup join (semi)
-- │ table: c@idx1
-- │ equality: (k) = (p_k)
-- │
-- └── • except all
-- │
-- ├── • scan buffer
-- │ label: buffer 1
-- │
-- └── • scan buffer
-- label: buffer 1
-- (35 rows)
```
## 2. Do not use an invisible index for FK cascades if there is a duplicate, non-visible index.
This case is similar to the previous. The invisible `idx1` is used for the FK cascades instead of the duplicate, visible `idx2`.
```sql
CREATE TABLE p (
k INT PRIMARY KEY
);
CREATE TABLE c (
k INT PRIMARY KEY,
p_k INT REFERENCES p(k) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX idx1 (p_k) NOT VISIBLE,
INDEX idx2 (p_k)
);
SET prefer_lookup_joins_for_fks = true;
EXPLAIN
DELETE FROM p WHERE k = 1;
-- info
-- ----------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • delete range
-- │ from: p
-- │ spans: [/1 - /1]
-- │
-- └── • fk-cascade
-- │ fk: c_p_k_fkey
-- │
-- └── • delete
-- │ from: c
-- │
-- └── • scan
-- missing stats
-- table: c@idx1
-- spans: [/1 - /1]
-- (19 rows)
EXPLAIN
UPDATE p SET k = 2 WHERE k = 1;
-- info
-- --------------------------------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • update
-- │ │ table: p
-- │ │ set: k
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • render
-- │ │
-- │ └── • scan
-- │ missing stats
-- │ table: p@p_pkey
-- │ spans: [/1 - /1]
-- │ locking strength: for update
-- │
-- └── • fk-cascade
-- │ fk: c_p_k_fkey
-- │
-- └── • root
-- │
-- ├── • update
-- │ │ table: c
-- │ │ set: p_k
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • hash join
-- │ │ equality: (p_k) = (k)
-- │ │
-- │ ├── • scan
-- │ │ missing stats
-- │ │ table: c@c_pkey
-- │ │ spans: FULL SCAN
-- │ │
-- │ └── • filter
-- │ │ estimated row count: 33
-- │ │ filter: k IS DISTINCT FROM k_new
-- │ │
-- │ └── • scan buffer
-- │ estimated row count: 100
-- │ label: buffer 1000000
-- │
-- └── • constraint-check
-- │
-- └── • error if rows
-- │
-- └── • lookup join (anti)
-- │ table: p@p_pkey
-- │ equality: (k_new) = (k)
-- │ equality cols are key
-- │
-- └── • filter
-- │ filter: k_new IS NOT NULL
-- │
-- └── • scan buffer
-- label: buffer 1
-- (62 rows)
```
---
### 3. A more general case of (1) and (2): Do not use an invisible index for FK constraint checks and cascades if there is a visible index that can be constrained to the same number of rows
In the example below, the visible `idx2` is not a duplicate of the invisible `idx1`, but it can be constrained to the same number of rows, so it should be chosen.
```sql
CREATE TABLE p (
k INT PRIMARY KEY
);
CREATE TABLE c (
k INT PRIMARY KEY,
p_k INT REFERENCES p(k),
a INT,
INDEX idx1 (p_k) NOT VISIBLE,
INDEX idx2 (p_k, a)
);
EXPLAIN
DELETE FROM p WHERE k = 1;
-- info
-- ---------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • delete
-- │ │ from: p
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • scan
-- │ missing stats
-- │ table: p@p_pkey
-- │ spans: [/1 - /1]
-- │
-- └── • constraint-check
-- │
-- └── • error if rows
-- │
-- └── • lookup join (semi)
-- │ table: c@idx1
-- │ equality: (k) = (p_k)
-- │
-- └── • scan buffer
-- label: buffer 1
-- (26 rows)
EXPLAIN
UPDATE p SET k = 2 WHERE k = 1;
-- info
-- --------------------------------------------------
-- distribution: local
-- vectorized: true
--
-- • root
-- │
-- ├── • update
-- │ │ table: p
-- │ │ set: k
-- │ │
-- │ └── • buffer
-- │ │ label: buffer 1
-- │ │
-- │ └── • render
-- │ │
-- │ └── • scan
-- │ missing stats
-- │ table: p@p_pkey
-- │ spans: [/1 - /1]
-- │ locking strength: for update
-- │
-- └── • constraint-check
-- │
-- └── • error if rows
-- │
-- └── • lookup join (semi)
-- │ table: c@idx1
-- │ equality: (k) = (p_k)
-- │
-- └── • except all
-- │
-- ├── • scan buffer
-- │ label: buffer 1
-- │
-- └── • scan buffer
-- label: buffer 1
-- (35 rows)
```
Jira issue: CRDB-44333
Contributor guide
Assessment
This issue has not been assessed yet.