Skip to content

Commit 5800907

Browse files
committed
execbuild: enable lock optimizations for multi-family tables
In #114401 we added some simple optimizations to push locking down into input operations for SELECT FOR UPDATE under Read Committed isolation. In #116170 we disabled these optimizations for multi-column-family tables because we weren't sure that all necessary column families would be locked. This commit enables the optimizations for multi-column-family tables, after confirming that all families needing to be locked are fetched by the input operation. Informs: #114566, #116838 Release note: None
1 parent 0b1d979 commit 5800907

File tree

3 files changed

+146
-42
lines changed

3 files changed

+146
-42
lines changed

pkg/sql/opt/exec/execbuilder/mutation.go

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,37 +1337,47 @@ func (b *Builder) buildLock(lock *memo.LockExpr) (_ execPlan, outputCols colOrdM
13371337

13381338
// In some simple cases we can push the locking down into the input operation
13391339
// instead of adding what would be a redundant lookup join. We only apply
1340-
// these optimizations for single-column-family tables.
1340+
// these optimizations when all necessary column families would be locked by
1341+
// the input operation.
13411342
//
13421343
// TODO(michae2): To optimize more complex cases, such as a Project on top of
13431344
// a Scan, or multiple Lock ops, etc, we need to do something similar to
13441345
// ordering. That is, make locking a physical property required by Lock, and
13451346
// make various operators provide the physical property, with a
13461347
// locking-semi-LookupJoin as the enforcer of last resort.
13471348
tab := b.mem.Metadata().Table(lock.Table)
1348-
if tab.FamilyCount() == 1 {
1349-
switch input := lock.Input.(type) {
1350-
case *memo.ScanExpr:
1351-
if input.Table == lock.KeySource && input.Index == cat.PrimaryIndex {
1352-
// Make a shallow copy of the scan to avoid mutating the original.
1353-
scan := *input
1354-
scan.Locking = scan.Locking.Max(locking)
1355-
return b.buildRelational(&scan)
1356-
}
1357-
case *memo.IndexJoinExpr:
1358-
if input.Table == lock.KeySource {
1359-
// Make a shallow copy of the join to avoid mutating the original.
1360-
join := *input
1361-
join.Locking = join.Locking.Max(locking)
1362-
return b.buildRelational(&join)
1363-
}
1364-
case *memo.LookupJoinExpr:
1365-
if input.Table == lock.KeySource && input.Index == cat.PrimaryIndex &&
1366-
(input.JoinType == opt.InnerJoinOp || input.JoinType == opt.SemiJoinOp) &&
1367-
!input.IsFirstJoinInPairedJoiner && !input.IsSecondJoinInPairedJoiner &&
1368-
// We con't push the locking down if the lookup join has additional on
1369-
// predicates that will filter out rows after the join.
1370-
len(input.On) == 0 {
1349+
srcTab := b.mem.Metadata().Table(lock.KeySource)
1350+
lockFamilies := opt.FamiliesForCols(tab, lock.Table, lock.LockCols)
1351+
switch input := lock.Input.(type) {
1352+
case *memo.ScanExpr:
1353+
if input.Table == lock.KeySource && input.Index == cat.PrimaryIndex &&
1354+
// Check that all necessary column families would be locked.
1355+
colFamiliesContainLockFamilies(srcTab, lock.KeySource, input.Cols, lockFamilies) {
1356+
// Make a shallow copy of the scan to avoid mutating the original.
1357+
scan := *input
1358+
scan.Locking = scan.Locking.Max(locking)
1359+
return b.buildRelational(&scan)
1360+
}
1361+
case *memo.IndexJoinExpr:
1362+
if input.Table == lock.KeySource &&
1363+
// Check that all necessary column families would be locked.
1364+
colFamiliesContainLockFamilies(srcTab, lock.KeySource, input.Cols, lockFamilies) {
1365+
// Make a shallow copy of the join to avoid mutating the original.
1366+
join := *input
1367+
join.Locking = join.Locking.Max(locking)
1368+
return b.buildRelational(&join)
1369+
}
1370+
case *memo.LookupJoinExpr:
1371+
if input.Table == lock.KeySource && input.Index == cat.PrimaryIndex &&
1372+
(input.JoinType == opt.InnerJoinOp || input.JoinType == opt.SemiJoinOp) &&
1373+
!input.IsFirstJoinInPairedJoiner && !input.IsSecondJoinInPairedJoiner &&
1374+
// We con't push the locking down if the lookup join has additional on
1375+
// predicates that will filter out rows after the join.
1376+
len(input.On) == 0 {
1377+
// Check that all necessary column families would be locked.
1378+
joinInputCols := input.Input.Relational().OutputCols
1379+
lookupCols := input.Cols.Difference(joinInputCols)
1380+
if colFamiliesContainLockFamilies(srcTab, lock.KeySource, lookupCols, lockFamilies) {
13711381
// Make a shallow copy of the join to avoid mutating the original.
13721382
join := *input
13731383
join.Locking = join.Locking.Max(locking)
@@ -1393,6 +1403,18 @@ func (b *Builder) buildLock(lock *memo.LockExpr) (_ execPlan, outputCols colOrdM
13931403
return b.buildLookupJoin(join)
13941404
}
13951405

1406+
// colFamiliesContainLockFamilies checks that the families for cols include all
1407+
// of the lock families.
1408+
func colFamiliesContainLockFamilies(
1409+
tab cat.Table, tabID opt.TableID, cols opt.ColSet, lockFamilies intsets.Fast,
1410+
) bool {
1411+
if tab.FamilyCount() == 1 {
1412+
return true
1413+
}
1414+
families := opt.FamiliesForCols(tab, tabID, cols)
1415+
return lockFamilies.SubsetOf(families)
1416+
}
1417+
13961418
func (b *Builder) setMutationFlags(e memo.RelExpr) {
13971419
b.flags.Set(exec.PlanFlagContainsMutation)
13981420
switch e.Op() {

pkg/sql/opt/exec/execbuilder/testdata/select_for_update

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,7 +2929,8 @@ vectorized: true
29292929
table: t@t_pkey
29302930
spans: /5/0
29312931

2932-
# Table with multiple column families. Cannot push down locking.
2932+
# Table with multiple column families. Test that we can push down locking in
2933+
# some cases.
29332934
statement ok
29342935
CREATE TABLE xyz (
29352936
x INT PRIMARY KEY,
@@ -2940,6 +2941,7 @@ CREATE TABLE xyz (
29402941
FAMILY (z)
29412942
)
29422943

2944+
# We can't push down locking if the scan doesn't fetch all families.
29432945
query T
29442946
EXPLAIN (VERBOSE) SELECT y FROM xyz WHERE x = 1 FOR UPDATE
29452947
----
@@ -2963,48 +2965,93 @@ vectorized: true
29632965
table: xyz@xyz_pkey
29642966
spans: /1/0
29652967

2968+
# We can push down locking if the scan fetches all families.
2969+
query T
2970+
EXPLAIN (VERBOSE) SELECT y, z FROM xyz WHERE x = 1 FOR UPDATE
2971+
----
2972+
distribution: local
2973+
vectorized: true
2974+
·
2975+
• project
2976+
│ columns: (y, z)
2977+
2978+
└── • scan
2979+
columns: (x, y, z)
2980+
estimated row count: 1 (missing stats)
2981+
table: xyz@xyz_pkey
2982+
spans: /1-/2
2983+
locking strength: for update
2984+
2985+
# We can't push down locking if the index join doesn't fetch all families.
2986+
query T
2987+
EXPLAIN (VERBOSE) SELECT y FROM xyz WHERE z = 2 FOR SHARE
2988+
----
2989+
distribution: local
2990+
vectorized: true
2991+
·
2992+
• project
2993+
│ columns: (y)
2994+
2995+
└── • lookup join (semi)
2996+
│ columns: (x, y)
2997+
│ estimated row count: 10 (missing stats)
2998+
│ table: xyz@xyz_pkey
2999+
│ equality: (x) = (x)
3000+
│ equality cols are key
3001+
│ locking strength: for share
3002+
3003+
└── • project
3004+
│ columns: (x, y)
3005+
3006+
└── • index join
3007+
│ columns: (x, y, z)
3008+
│ estimated row count: 10 (missing stats)
3009+
│ table: xyz@xyz_pkey
3010+
│ key columns: x
3011+
3012+
└── • scan
3013+
columns: (x, z)
3014+
estimated row count: 10 (missing stats)
3015+
table: xyz@xyz_z_idx
3016+
spans: /2-/3
3017+
3018+
# We can push down locking if the index join fetches all families.
29663019
query T
29673020
EXPLAIN (VERBOSE) SELECT * FROM xyz WHERE z = 2 FOR SHARE
29683021
----
29693022
distribution: local
29703023
vectorized: true
29713024
·
2972-
lookup join (semi)
3025+
index join
29733026
│ columns: (x, y, z)
29743027
│ estimated row count: 10 (missing stats)
29753028
│ table: xyz@xyz_pkey
2976-
│ equality: (x) = (x)
2977-
│ equality cols are key
3029+
│ key columns: x
29783030
│ locking strength: for share
29793031
2980-
└── • index join
2981-
│ columns: (x, y, z)
2982-
│ estimated row count: 10 (missing stats)
2983-
│ table: xyz@xyz_pkey
2984-
│ key columns: x
2985-
2986-
└── • scan
2987-
columns: (x, z)
2988-
estimated row count: 10 (missing stats)
2989-
table: xyz@xyz_z_idx
2990-
spans: /2-/3
3032+
└── • scan
3033+
columns: (x, z)
3034+
estimated row count: 10 (missing stats)
3035+
table: xyz@xyz_z_idx
3036+
spans: /2-/3
29913037

3038+
# We can't push down locking if the lookup join doesn't fetch all families.
29923039
query T
2993-
EXPLAIN (VERBOSE) SELECT * FROM t INNER LOOKUP JOIN xyz ON x = b WHERE a = 5 FOR UPDATE OF xyz
3040+
EXPLAIN (VERBOSE) SELECT t.*, x FROM t INNER LOOKUP JOIN xyz ON x = b WHERE a = 5 FOR UPDATE OF xyz
29943041
----
29953042
distribution: local
29963043
vectorized: true
29973044
·
29983045
• lookup join (semi)
2999-
│ columns: (a, b, x, y, z)
3046+
│ columns: (a, b, x)
30003047
│ estimated row count: 1 (missing stats)
30013048
│ table: xyz@xyz_pkey
30023049
│ equality: (x) = (x)
30033050
│ equality cols are key
30043051
│ locking strength: for update
30053052
30063053
└── • lookup join (inner)
3007-
│ columns: (a, b, x, y, z)
3054+
│ columns: (a, b, x)
30083055
│ estimated row count: 1 (missing stats)
30093056
│ table: xyz@xyz_pkey
30103057
│ equality: (b) = (x)
@@ -3016,5 +3063,26 @@ vectorized: true
30163063
table: t@t_pkey
30173064
spans: /5/0
30183065

3066+
# We can push down locking if the lookup join fetches all families.
3067+
query T
3068+
EXPLAIN (VERBOSE) SELECT * FROM t INNER LOOKUP JOIN xyz ON x = b WHERE a = 5 FOR UPDATE OF xyz
3069+
----
3070+
distribution: local
3071+
vectorized: true
3072+
·
3073+
• lookup join (inner)
3074+
│ columns: (a, b, x, y, z)
3075+
│ estimated row count: 1 (missing stats)
3076+
│ table: xyz@xyz_pkey
3077+
│ equality: (b) = (x)
3078+
│ equality cols are key
3079+
│ locking strength: for update
3080+
3081+
└── • scan
3082+
columns: (a, b)
3083+
estimated row count: 1 (missing stats)
3084+
table: t@t_pkey
3085+
spans: /5/0
3086+
30193087
statement ok
30203088
RESET optimizer_use_lock_op_for_serializable

pkg/sql/opt/util.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,17 @@ func GetAllFKsAmongTables(
160160
}
161161
return addFKs
162162
}
163+
164+
// FamiliesForCols returns the set of column families for the set of cols.
165+
func FamiliesForCols(tab cat.Table, tabID TableID, cols ColSet) (families intsets.Fast) {
166+
for i, n := 0, tab.FamilyCount(); i < n; i++ {
167+
family := tab.Family(i)
168+
for j, m := 0, family.ColumnCount(); j < m; j++ {
169+
if cols.Contains(tabID.ColumnID(family.Column(j).Ordinal)) {
170+
families.Add(i)
171+
break
172+
}
173+
}
174+
}
175+
return families
176+
}

0 commit comments

Comments
 (0)