Skip to content

Commit

Permalink
Merge #56007
Browse files Browse the repository at this point in the history
56007: opt: prune unnecessary check columns r=RaduBerinde a=mgartner

This commit updates the optimizer to prune synthesized `CHECK`
constraint columns for `UPDATES` when columns referenced in the
constraints are not updated. This may also allow the optimizer to no
longer fetch those referenced columns.

This should provide a performance benefit for `UDPATE`s to tables with
check constraints. Notably, tables that have been given many column
families (in order to reduce contention) should see a significant
reduction in contention for `UPDATE`s that mutate a subset of column
families.

Informs #51526

Release note (performance improvement): Previously, all `CHECK`
constraints defined on a table would be tested for every `UPDATE` to the
table. Now, a check constraint will not be tested for validity when the
values of columns it references are not being updated. The referenced
columns are no longer fetchecd in cases where they were only fetched to
test `CHECK` constraints.

Co-authored-by: Marcus Gartner <marcus@cockroachlabs.com>
  • Loading branch information
craig[bot] and mgartner committed Oct 28, 2020
2 parents c5b2cfc + d9a906e commit db07523
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 191 deletions.
89 changes: 43 additions & 46 deletions pkg/sql/opt/exec/execbuilder/testdata/check_constraints
Original file line number Diff line number Diff line change
Expand Up @@ -21,53 +21,50 @@ CREATE TABLE t9 (
query TTTTT
EXPLAIN (VERBOSE) UPDATE t9 SET b = b + 1 WHERE a = 5
----
· distribution local · ·
· vectorized false · ·
update · · () ·
│ estimated row count 0 (missing stats) · ·
│ table t9 · ·
│ set b · ·
│ auto commit · · ·
└── render · · (a, b, b_new, check1, check2) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 a > b_new · ·
│ render 1 d IS NULL · ·
│ render 2 a · ·
│ render 3 b · ·
│ render 4 b_new · ·
└── render · · (b_new, a, b, d) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 b + 1 · ·
│ render 1 a · ·
│ render 2 b · ·
│ render 3 d · ·
└── scan · · (a, b, d) ·
· estimated row count 1 (missing stats) · ·
· table t9@primary · ·
· spans /5/0-/5/1/2 /5/3/1-/5/3/2 · ·
· distribution local · ·
· vectorized false · ·
update · · () ·
│ estimated row count 0 (missing stats) · ·
│ table t9 · ·
│ set b · ·
│ auto commit · · ·
└── render · · (a, b, b_new, check1) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 a > b_new · ·
│ render 1 a · ·
│ render 2 b · ·
│ render 3 b_new · ·
└── render · · (b_new, a, b) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 b + 1 · ·
│ render 1 a · ·
│ render 2 b · ·
└── scan · · (a, b) ·
· estimated row count 1 (missing stats) · ·
· table t9@primary · ·
· spans /5/0-/5/1/2 · ·

query TTTTT
EXPLAIN (VERBOSE) UPDATE t9 SET a = 2 WHERE a = 5
----
· distribution local · ·
· vectorized false · ·
update · · () ·
│ estimated row count 0 (missing stats) · ·
│ table t9 · ·
│ set a · ·
│ auto commit · · ·
└── render · · (a, b, c, d, e, a_new, check1, check2) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 b < 2 · ·
│ render 1 d IS NULL · ·
│ render 2 2 · ·
│ render 3 a · ·
│ render 4 b · ·
│ render 5 c · ·
│ render 6 d · ·
│ render 7 e · ·
└── scan · · (a, b, c, d, e) ·
· estimated row count 1 (missing stats) · ·
· table t9@primary · ·
· spans /5-/5/# · ·
· locking strength for update · ·
· distribution local · ·
· vectorized false · ·
update · · () ·
│ estimated row count 0 (missing stats) · ·
│ table t9 · ·
│ set a · ·
│ auto commit · · ·
└── render · · (a, b, c, d, e, a_new, check1) ·
│ estimated row count 1 (missing stats) · ·
│ render 0 b < 2 · ·
│ render 1 2 · ·
│ render 2 a · ·
│ render 3 b · ·
│ render 4 c · ·
│ render 5 d · ·
│ render 6 e · ·
└── scan · · (a, b, c, d, e) ·
· estimated row count 1 (missing stats) · ·
· table t9@primary · ·
· spans /5-/5/# · ·
· locking strength for update · ·
170 changes: 170 additions & 0 deletions pkg/sql/opt/norm/testdata/rules/prune_cols
Original file line number Diff line number Diff line change
Expand Up @@ -2388,6 +2388,176 @@ upsert mutation
└── projections
└── CASE WHEN a:11 IS NULL THEN column2:8 ELSE 10 END [as=upsert_b:19, outer=(8,11)]

exec-ddl
CREATE TABLE checks (
a INT PRIMARY KEY,
b INT DEFAULT 20,
c INT,
d INT,
CHECK (a > 0),
CHECK (b > 10),
CHECK (c > b)
)
----

# Prune all check columns when none of the referenced columns are updated.
norm expect=PruneMutationInputCols
UPDATE checks SET d = 0
----
update checks
├── columns: <none>
├── fetch columns: a:6 b:7 c:8 d:9
├── update-mapping:
│ └── d_new:11 => d:4
├── cardinality: [0 - 0]
├── volatile, mutations
└── project
├── columns: d_new:11!null a:6!null b:7 c:8 d:9
├── key: (6)
├── fd: ()-->(11), (6)-->(7-9)
├── scan checks
│ ├── columns: a:6!null b:7 c:8 d:9
│ ├── check constraint expressions
│ │ └── a:6 > 0 [outer=(6), constraints=(/6: [/1 - ]; tight)]
│ ├── key: (6)
│ └── fd: (6)-->(7-9)
└── projections
└── 0 [as=d_new:11]

# Do not prune check columns when their referenced columns are updated.
norm expect=PruneMutationInputCols
UPDATE checks SET b = 5
----
update checks
├── columns: <none>
├── fetch columns: a:6 b:7 c:8 d:9
├── update-mapping:
│ └── b_new:11 => b:2
├── check columns: check2:13 check3:14
├── cardinality: [0 - 0]
├── volatile, mutations
└── project
├── columns: check2:13!null check3:14 b_new:11!null a:6!null b:7 c:8 d:9
├── key: (6)
├── fd: ()-->(11,13), (6)-->(7-9), (8)-->(14)
├── scan checks
│ ├── columns: a:6!null b:7 c:8 d:9
│ ├── check constraint expressions
│ │ └── a:6 > 0 [outer=(6), constraints=(/6: [/1 - ]; tight)]
│ ├── key: (6)
│ └── fd: (6)-->(7-9)
└── projections
├── false [as=check2:13]
├── c:8 > 5 [as=check3:14, outer=(8)]
└── 5 [as=b_new:11]


# Do not prune check columns for an insert.
norm expect-not=PruneMutationInputCols
INSERT INTO checks (a, c, d) VALUES (1, 3, 4)
----
insert checks
├── columns: <none>
├── insert-mapping:
│ ├── column1:6 => a:1
│ ├── column9:9 => b:2
│ ├── column2:7 => c:3
│ └── column3:8 => d:4
├── check columns: check1:10 check2:11 check3:12
├── cardinality: [0 - 0]
├── volatile, mutations
└── values
├── columns: column1:6!null column2:7!null column3:8!null column9:9!null check1:10!null check2:11!null check3:12!null
├── cardinality: [1 - 1]
├── key: ()
├── fd: ()-->(6-12)
└── (1, 3, 4, 20, true, true, false)

# Do not prune check columns for an upsert that does not require a scan.
norm expect-not=PruneMutationInputCols
UPSERT INTO checks (a, b, c, d) VALUES (1, 2, 3, 4)
----
upsert checks
├── columns: <none>
├── upsert-mapping:
│ ├── column1:6 => a:1
│ ├── column2:7 => b:2
│ ├── column3:8 => c:3
│ └── column4:9 => d:4
├── check columns: check1:10 check2:11 check3:12
├── cardinality: [0 - 0]
├── volatile, mutations
└── values
├── columns: column1:6!null column2:7!null column3:8!null column4:9!null check1:10!null check2:11!null check3:12!null
├── cardinality: [1 - 1]
├── key: ()
├── fd: ()-->(6-12)
└── (1, 2, 3, 4, true, false, true)

# Do not prune check columns for an upsert that requires a scan.
norm
UPSERT INTO checks (a, c, d) VALUES (1, 3, 4)
----
upsert checks
├── columns: <none>
├── arbiter indexes: primary
├── canary column: a:10
├── fetch columns: a:10 b:11 c:12 d:13
├── insert-mapping:
│ ├── column1:6 => a:1
│ ├── column9:9 => b:2
│ ├── column2:7 => c:3
│ └── column3:8 => d:4
├── update-mapping:
│ ├── column2:7 => c:3
│ └── column3:8 => d:4
├── check columns: check1:17 check2:18 check3:19
├── cardinality: [0 - 0]
├── volatile, mutations
└── project
├── columns: check1:17 check2:18 check3:19 column1:6!null column2:7!null column3:8!null column9:9!null a:10 b:11 c:12 d:13
├── cardinality: [1 - 1]
├── key: ()
├── fd: ()-->(6-13,17-19)
├── project
│ ├── columns: upsert_a:15 upsert_b:16 column1:6!null column2:7!null column3:8!null column9:9!null a:10 b:11 c:12 d:13
│ ├── cardinality: [1 - 1]
│ ├── key: ()
│ ├── fd: ()-->(6-13,15,16)
│ ├── left-join (cross)
│ │ ├── columns: column1:6!null column2:7!null column3:8!null column9:9!null a:10 b:11 c:12 d:13
│ │ ├── cardinality: [1 - 1]
│ │ ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
│ │ ├── key: ()
│ │ ├── fd: ()-->(6-13)
│ │ ├── values
│ │ │ ├── columns: column1:6!null column2:7!null column3:8!null column9:9!null
│ │ │ ├── cardinality: [1 - 1]
│ │ │ ├── key: ()
│ │ │ ├── fd: ()-->(6-9)
│ │ │ └── (1, 3, 4, 20)
│ │ ├── select
│ │ │ ├── columns: a:10!null b:11 c:12 d:13
│ │ │ ├── cardinality: [0 - 1]
│ │ │ ├── key: ()
│ │ │ ├── fd: ()-->(10-13)
│ │ │ ├── scan checks
│ │ │ │ ├── columns: a:10!null b:11 c:12 d:13
│ │ │ │ ├── check constraint expressions
│ │ │ │ │ └── a:10 > 0 [outer=(10), constraints=(/10: [/1 - ]; tight)]
│ │ │ │ ├── key: (10)
│ │ │ │ └── fd: (10)-->(11-13)
│ │ │ └── filters
│ │ │ └── a:10 = 1 [outer=(10), constraints=(/10: [/1 - /1]; tight), fd=()-->(10)]
│ │ └── filters (true)
│ └── projections
│ ├── CASE WHEN a:10 IS NULL THEN column1:6 ELSE a:10 END [as=upsert_a:15, outer=(6,10)]
│ └── CASE WHEN a:10 IS NULL THEN column9:9 ELSE b:11 END [as=upsert_b:16, outer=(9-11)]
└── projections
├── upsert_a:15 > 0 [as=check1:17, outer=(15)]
├── upsert_b:16 > 10 [as=check2:18, outer=(16)]
└── column2:7 > upsert_b:16 [as=check3:19, outer=(7,16)]

# ------------------------------------------------------------------------------
# PruneMutationReturnCols
# ------------------------------------------------------------------------------
Expand Down
29 changes: 27 additions & 2 deletions pkg/sql/opt/optbuilder/mutation_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ func (mb *mutationBuilder) addCheckConstraintCols() {
if mb.tab.CheckCount() != 0 {
projectionsScope := mb.outScope.replace()
projectionsScope.appendColumnsFromScope(mb.outScope)
mutationCols := mb.mutationColumnIDs()

for i, n := 0, mb.tab.CheckCount(); i < n; i++ {
expr, err := parser.ParseExpr(mb.tab.Check(i).Constraint)
Expand All @@ -756,15 +757,39 @@ func (mb *mutationBuilder) addCheckConstraintCols() {

// TODO(ridwanmsharif): Maybe we can avoid building constraints here
// and instead use the constraints stored in the table metadata.
mb.b.buildScalar(texpr, mb.outScope, projectionsScope, scopeCol, nil)
mb.checkColIDs[i] = scopeCol.id
referencedCols := &opt.ColSet{}
mb.b.buildScalar(texpr, mb.outScope, projectionsScope, scopeCol, referencedCols)

// Synthesized check columns are only necessary if the columns
// referenced in the check expression are being mutated. If they are
// not being mutated, we do not add the newly built column to
// checkColIDs. This allows pruning normalization rules to remove
// the unnecessary projected column.
if referencedCols.Intersects(mutationCols) {
mb.checkColIDs[i] = scopeCol.id
}
}

mb.b.constructProjectForScope(mb.outScope, projectionsScope)
mb.outScope = projectionsScope
}
}

// mutationColumnIDs returns the set of all column IDs that will be mutated.
func (mb *mutationBuilder) mutationColumnIDs() opt.ColSet {
cols := opt.ColSet{}
for _, col := range mb.insertColIDs {
cols.Add(col)
}
for _, col := range mb.updateColIDs {
cols.Add(col)
}
for _, col := range mb.upsertColIDs {
cols.Add(col)
}
return cols
}

// projectPartialIndexPutCols builds a Project that synthesizes boolean output
// columns for each partial index defined on the target table. The execution
// code uses these booleans to determine whether or not to add a row in the
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/opt/optbuilder/testdata/update
Original file line number Diff line number Diff line change
Expand Up @@ -1548,7 +1548,7 @@ update checks
├── update-mapping:
│ ├── b_new:11 => b:2
│ └── column12:12 => d:4
├── check columns: check1:13 check2:14
├── check columns: check1:13
└── project
├── columns: check1:13 check2:14!null a:6!null b:7 c:8 d:9 crdb_internal_mvcc_timestamp:10 b_new:11!null column12:12
├── project
Expand Down
Loading

0 comments on commit db07523

Please sign in to comment.