cockroachdb / cockroachdb/cockroach

opt: invisible index improvements

Open
#135,018 1 comment 0 reactions 0 assignees View on GitHub
C-enhancement O-support P-3 T-sql-queries
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

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.