Skip to content

Commit de71321

Browse files
committed
opt: explore splitting disjunction over same column
The SplitDisjunction rules did not consider disjunctions over expressions referencing the same column interesting, because normally these disjunctions can become multiple spans in the same constrained scan. There is one special case, however, where disjunctions over the same column might be interesting: if the table has multiple partial indexes with different predicates referencing that column. In that case we might be able to use a different partial index for each side of the disjunction. Fixes: #157073 Release note (performance improvement): This commit teaches the optimizer to split disjunctions on the same column into unions when there are multiple partial indexes with different predicates referencing that column.
1 parent 6816c43 commit de71321

File tree

2 files changed

+462
-4
lines changed

2 files changed

+462
-4
lines changed

pkg/sql/opt/xform/select_funcs.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@ func (c *CustomFuncs) SplitDisjunction(
19411941
// An "interesting" pair of expressions is one where:
19421942
//
19431943
// 1. The column sets of both expressions in the pair are not
1944-
// equal.
1944+
// equal, and
19451945
// 2. Two index scans can potentially be constrained by both expressions in
19461946
// the pair.
19471947
//
@@ -1960,6 +1960,13 @@ func (c *CustomFuncs) SplitDisjunction(
19601960
// There is no possible "interesting" pair here because the left and right sides
19611961
// of the disjunction share the same columns.
19621962
//
1963+
// There is one exceptional case when a pair could be interesting even with
1964+
// equal column sets for both expressions: when the table itself contains
1965+
// multiple partial indexes with different predicates referencing the same
1966+
// column. In this case we might be able to use different partial indexes for
1967+
// both expressions, and so consider a pair interesting even with equal column
1968+
// sets.
1969+
//
19631970
// findInterestingDisjunctionPair groups all sub-expressions adjacent to the
19641971
// input's top-level OrExpr into left and right expression groups. These two
19651972
// groups form the new filter expressions on the left and right side of the
@@ -1997,11 +2004,14 @@ func (c *CustomFuncs) findInterestingDisjunctionPair(
19972004
// not match) on.
19982005
if leftColSet.Empty() {
19992006
leftColSet = cols
2007+
leftExprs = append(leftExprs, expr)
2008+
return
20002009
}
20012010

2002-
// If the current expression ColSet matches leftColSet, add the expr to
2003-
// the left group. Otherwise, add it to the right group.
2004-
if leftColSet.Equals(cols) {
2011+
// If the current expression ColSet matches leftColSet (and we're not using
2012+
// the exception for multiple referencing partial index predicates) add the
2013+
// expr to the left group. Otherwise, add it to the right group.
2014+
if leftColSet.Equals(cols) && !c.multiplePartialIndexesReferencing(sp, leftColSet) {
20052015
leftExprs = append(leftExprs, expr)
20062016
} else {
20072017
rightColSet.UnionWith(cols)
@@ -2095,6 +2105,56 @@ func (c *CustomFuncs) canMaybeConstrainIndexWithCols(
20952105
return false
20962106
}
20972107

2108+
// multiplePartialIndexesReferencing returns true if at least one of the columns
2109+
// is referenced by the predicates of multiple partial indexes. For example,
2110+
// given this table:
2111+
//
2112+
// CREATE TABLE abc (
2113+
// a INT NOT NULL,
2114+
// b INT NOT NULL,
2115+
// c INT NOT NULL,
2116+
// INDEX (a) WHERE b > 10,
2117+
// INDEX (a) WHERE b != 100 AND c < 1000,
2118+
// INDEX (c) WHERE a > 5 AND a % 2 = 0
2119+
// )
2120+
//
2121+
// Then multiplePartialIndexesReferencing will return true if called with (b) or
2122+
// (a, b) or (b, c) or (a, b, c) but will return false if called with (a) or (c)
2123+
// or (a, c).
2124+
func (c *CustomFuncs) multiplePartialIndexesReferencing(
2125+
scanPrivate *memo.ScanPrivate, cols opt.ColSet,
2126+
) bool {
2127+
md := c.e.mem.Metadata()
2128+
tabMeta := md.TableMeta(scanPrivate.Table)
2129+
2130+
var prevPartialIndexPredCols opt.ColSet
2131+
2132+
// Iterate through all partial indexes of the table and return true if one of
2133+
// the columns is referenced again after being referenced by a previous
2134+
// partial index.
2135+
for i := 0; i < tabMeta.Table.IndexCount(); i++ {
2136+
index := tabMeta.Table.Index(i)
2137+
if _, isPartialIndex := index.Predicate(); isPartialIndex {
2138+
p, ok := tabMeta.PartialIndexPredicate(i)
2139+
if !ok {
2140+
// A partial index predicate expression was not built for the
2141+
// partial index. See Builder.buildScan for details on when this
2142+
// can occur.
2143+
continue
2144+
}
2145+
pred := *p.(*memo.FiltersExpr)
2146+
partialIndexPredCols := pred.OuterCols().Intersection(cols)
2147+
// If one of the columns has now been referenced a second time, return
2148+
// true.
2149+
if partialIndexPredCols.Intersects(prevPartialIndexPredCols) {
2150+
return true
2151+
}
2152+
prevPartialIndexPredCols.UnionWith(partialIndexPredCols)
2153+
}
2154+
}
2155+
return false
2156+
}
2157+
20982158
// MakeSetPrivate constructs a new SetPrivate with given left, right, and out
20992159
// columns.
21002160
func (c *CustomFuncs) MakeSetPrivate(left, right, out opt.ColSet) *memo.SetPrivate {

0 commit comments

Comments
 (0)