diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index 05382d85f7c4..1dec3d06fc63 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -3908,6 +3908,10 @@ func (m *sessionDataMutator) SetLegacyVarcharTyping(val bool) { m.data.LegacyVarcharTyping = val } +func (m *sessionDataMutator) SetOptimizerPreferBoundedCardinality(b bool) { + m.data.OptimizerPreferBoundedCardinality = b +} + // Utility functions related to scrubbing sensitive information on SQL Stats. // quantizeCounts ensures that the Count field in the diff --git a/pkg/sql/logictest/testdata/logic_test/information_schema b/pkg/sql/logictest/testdata/logic_test/information_schema index 5e8f0ca1e4d8..ffbfed28b611 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -6316,6 +6316,7 @@ optimizer on optimizer_always_use_histograms on optimizer_hoist_uncorrelated_equality_subqueries on optimizer_merge_joins_enabled on +optimizer_prefer_bounded_cardinality off optimizer_prove_implication_with_virtual_computed_columns on optimizer_push_limit_into_project_filtered_scan on optimizer_push_offset_into_index_join on diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 33456e8e999e..78c68ec12565 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -2985,6 +2985,7 @@ opt_split_scan_limit 2048 N optimizer_always_use_histograms on NULL NULL NULL string optimizer_hoist_uncorrelated_equality_subqueries on NULL NULL NULL string optimizer_merge_joins_enabled on NULL NULL NULL string +optimizer_prefer_bounded_cardinality off NULL NULL NULL string optimizer_prove_implication_with_virtual_computed_columns on NULL NULL NULL string optimizer_push_limit_into_project_filtered_scan on NULL NULL NULL string optimizer_push_offset_into_index_join on NULL NULL NULL string @@ -3183,6 +3184,7 @@ opt_split_scan_limit 2048 N optimizer_always_use_histograms on NULL user NULL on on optimizer_hoist_uncorrelated_equality_subqueries on NULL user NULL on on optimizer_merge_joins_enabled on NULL user NULL on on +optimizer_prefer_bounded_cardinality off NULL user NULL off off optimizer_prove_implication_with_virtual_computed_columns on NULL user NULL on on optimizer_push_limit_into_project_filtered_scan on NULL user NULL on on optimizer_push_offset_into_index_join on NULL user NULL on on @@ -3380,6 +3382,7 @@ optimizer NULL NULL NULL optimizer_always_use_histograms NULL NULL NULL NULL NULL optimizer_hoist_uncorrelated_equality_subqueries NULL NULL NULL NULL NULL optimizer_merge_joins_enabled NULL NULL NULL NULL NULL +optimizer_prefer_bounded_cardinality NULL NULL NULL NULL NULL optimizer_prove_implication_with_virtual_computed_columns NULL NULL NULL NULL NULL optimizer_push_limit_into_project_filtered_scan NULL NULL NULL NULL NULL optimizer_push_offset_into_index_join NULL NULL NULL NULL NULL diff --git a/pkg/sql/logictest/testdata/logic_test/show_source b/pkg/sql/logictest/testdata/logic_test/show_source index 34d50c523fbe..84d1fd6b1474 100644 --- a/pkg/sql/logictest/testdata/logic_test/show_source +++ b/pkg/sql/logictest/testdata/logic_test/show_source @@ -131,6 +131,7 @@ opt_split_scan_limit 2048 optimizer_always_use_histograms on optimizer_hoist_uncorrelated_equality_subqueries on optimizer_merge_joins_enabled on +optimizer_prefer_bounded_cardinality off optimizer_prove_implication_with_virtual_computed_columns on optimizer_push_limit_into_project_filtered_scan on optimizer_push_offset_into_index_join on diff --git a/pkg/sql/opt/memo/cost.go b/pkg/sql/opt/memo/cost.go index 6dfd70265abc..8dbcda97903a 100644 --- a/pkg/sql/opt/memo/cost.go +++ b/pkg/sql/opt/memo/cost.go @@ -19,8 +19,12 @@ type Cost struct { // group members during testing, by setting their cost so high that any other // member will have a lower cost. var MaxCost = Cost{ - C: math.Inf(+1), - Flags: CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, + C: math.Inf(+1), + Flags: CostFlags{ + FullScanPenalty: true, + HugeCostPenalty: true, + UnboundedCardinality: true, + }, } // Less returns true if this cost is lower than the given cost. @@ -57,6 +61,10 @@ type CostFlags struct { // used when the optimizer is forced to use a particular plan, and will error // if it cannot be used. HugeCostPenalty bool + // UnboundedCardinality is true if the operator or any of its descendants + // have no guaranteed upperbound on the number of rows that they can + // produce. See props.AnyCardinality. + UnboundedCardinality bool } // Less returns true if these flags indicate a lower penalty than the other @@ -71,6 +79,9 @@ func (c CostFlags) Less(other CostFlags) bool { if c.FullScanPenalty != other.FullScanPenalty { return !c.FullScanPenalty } + if c.UnboundedCardinality != other.UnboundedCardinality { + return !c.UnboundedCardinality + } return false } @@ -78,9 +89,10 @@ func (c CostFlags) Less(other CostFlags) bool { func (c *CostFlags) Add(other CostFlags) { c.FullScanPenalty = c.FullScanPenalty || other.FullScanPenalty c.HugeCostPenalty = c.HugeCostPenalty || other.HugeCostPenalty + c.UnboundedCardinality = c.UnboundedCardinality || other.UnboundedCardinality } // Empty returns true if these flags are empty. func (c CostFlags) Empty() bool { - return !c.FullScanPenalty && !c.HugeCostPenalty + return !c.FullScanPenalty && !c.HugeCostPenalty && !c.UnboundedCardinality } diff --git a/pkg/sql/opt/memo/cost_test.go b/pkg/sql/opt/memo/cost_test.go index ed8235731ad2..c44e3247c7ad 100644 --- a/pkg/sql/opt/memo/cost_test.go +++ b/pkg/sql/opt/memo/cost_test.go @@ -35,6 +35,8 @@ func TestCostLess(t *testing.T) { {memo.MaxCost, memo.MaxCost, false}, {memo.MaxCost, memo.Cost{C: 1.0, Flags: memo.CostFlags{FullScanPenalty: true}}, false}, {memo.Cost{C: 1.0, Flags: memo.CostFlags{HugeCostPenalty: true}}, memo.MaxCost, true}, + {memo.Cost{C: 2.0, Flags: memo.CostFlags{}}, memo.Cost{C: 1.0, Flags: memo.CostFlags{UnboundedCardinality: true}}, true}, + {memo.Cost{C: 1.0, Flags: memo.CostFlags{UnboundedCardinality: true}}, memo.Cost{C: 2.0, Flags: memo.CostFlags{}}, false}, } for _, tc := range testCases { if tc.left.Less(tc.right) != tc.expected { @@ -72,6 +74,8 @@ func TestCostFlagsLess(t *testing.T) { {memo.CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, memo.CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, false}, {memo.CostFlags{FullScanPenalty: false}, memo.CostFlags{FullScanPenalty: true}, true}, {memo.CostFlags{HugeCostPenalty: false}, memo.CostFlags{HugeCostPenalty: true}, true}, + {memo.CostFlags{UnboundedCardinality: false}, memo.CostFlags{UnboundedCardinality: true}, true}, + {memo.CostFlags{UnboundedCardinality: true}, memo.CostFlags{UnboundedCardinality: false}, false}, } for _, tc := range testCases { if tc.left.Less(tc.right) != tc.expected { diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 0f05c3132cfb..fbeeeb88b1a0 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -872,6 +872,9 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { if cost.Flags.HugeCostPenalty { b.WriteString(" huge-cost-penalty") } + if cost.Flags.UnboundedCardinality { + b.WriteString(" unbounded-cardinality") + } tp.Child(b.String()) } } diff --git a/pkg/sql/opt/memo/memo.go b/pkg/sql/opt/memo/memo.go index 2e9879778342..4e532257d22f 100644 --- a/pkg/sql/opt/memo/memo.go +++ b/pkg/sql/opt/memo/memo.go @@ -199,6 +199,7 @@ type Memo struct { pushLimitIntoProjectFilteredScan bool unsafeAllowTriggersModifyingCascades bool legacyVarcharTyping bool + preferBoundedCardinality bool // txnIsoLevel is the isolation level under which the plan was created. This // affects the planning of some locking operations, so it must be included in @@ -289,6 +290,7 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) { pushLimitIntoProjectFilteredScan: evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan, unsafeAllowTriggersModifyingCascades: evalCtx.SessionData().UnsafeAllowTriggersModifyingCascades, legacyVarcharTyping: evalCtx.SessionData().LegacyVarcharTyping, + preferBoundedCardinality: evalCtx.SessionData().OptimizerPreferBoundedCardinality, txnIsoLevel: evalCtx.TxnIsoLevel, } m.metadata.Init() @@ -457,6 +459,7 @@ func (m *Memo) IsStale( m.pushLimitIntoProjectFilteredScan != evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan || m.unsafeAllowTriggersModifyingCascades != evalCtx.SessionData().UnsafeAllowTriggersModifyingCascades || m.legacyVarcharTyping != evalCtx.SessionData().LegacyVarcharTyping || + m.preferBoundedCardinality != evalCtx.SessionData().OptimizerPreferBoundedCardinality || m.txnIsoLevel != evalCtx.TxnIsoLevel { return true, nil } diff --git a/pkg/sql/opt/memo/memo_test.go b/pkg/sql/opt/memo/memo_test.go index c83fdca3ad1e..f3d1eecf4f0a 100644 --- a/pkg/sql/opt/memo/memo_test.go +++ b/pkg/sql/opt/memo/memo_test.go @@ -532,6 +532,11 @@ func TestMemoIsStale(t *testing.T) { evalCtx.SessionData().LegacyVarcharTyping = false notStale() + evalCtx.SessionData().OptimizerPreferBoundedCardinality = true + stale() + evalCtx.SessionData().OptimizerPreferBoundedCardinality = false + notStale() + // User no longer has access to view. catalog.View(tree.NewTableNameWithSchema("t", catconstants.PublicSchemaName, "abcview")).Revoked = true _, err = o.Memo().IsStale(ctx, &evalCtx, catalog) diff --git a/pkg/sql/opt/xform/coster.go b/pkg/sql/opt/xform/coster.go index 3c937971d4e9..4ecce1a6a5e3 100644 --- a/pkg/sql/opt/xform/coster.go +++ b/pkg/sql/opt/xform/coster.go @@ -619,8 +619,13 @@ func (c *coster) ComputeCost(candidate memo.RelExpr, required *physical.Required // Add a one-time cost for any operator with unbounded cardinality. This // ensures we prefer plans that push limits as far down the tree as possible, // all else being equal. + // + // Also add a cost flag for unbounded cardinality. if candidate.Relational().Cardinality.IsUnbounded() { cost.C += cpuCostFactor + if c.evalCtx.SessionData().OptimizerPreferBoundedCardinality { + cost.Flags.UnboundedCardinality = true + } } if !cost.Less(memo.MaxCost) { diff --git a/pkg/sql/opt/xform/testdata/coster/outside-histogram b/pkg/sql/opt/xform/testdata/coster/outside-histogram new file mode 100644 index 000000000000..0acd1fe544f4 --- /dev/null +++ b/pkg/sql/opt/xform/testdata/coster/outside-histogram @@ -0,0 +1,484 @@ +# This file contains tests for queries that filter columns beyond the max/min +# values in their histograms. Each query is tested multiple times with different +# settings to show the effect those settings have on the plan. +# + +exec-ddl +CREATE TABLE t ( + k INT PRIMARY KEY, + i INT, + s STRING, + INDEX (i), + INDEX (s) +) +---- + +exec-ddl +ALTER TABLE t INJECT STATISTICS '[ + { + "columns": ["k"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 1000, + "null_count": 0, + "avg_size": 2, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 1, "num_range": 99, "distinct_range": 99, "upper_bound": "100"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "200"}, + {"num_eq": 1, "num_range": 299, "distinct_range": 299, "upper_bound": "300"}, + {"num_eq": 1, "num_range": 399, "distinct_range": 399, "upper_bound": "400"} + ] + }, + { + "columns": ["i"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 44, + "null_count": 0, + "avg_size": 2, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 10, "num_range": 90, "distinct_range": 10, "upper_bound": "100"}, + {"num_eq": 10, "num_range": 190, "distinct_range": 10, "upper_bound": "200"}, + {"num_eq": 20, "num_range": 280, "distinct_range": 10, "upper_bound": "300"}, + {"num_eq": 30, "num_range": 370, "distinct_range": 10, "upper_bound": "400"} + ] + }, + { + "columns": ["s"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 40, + "null_count": 0, + "avg_size": 3, + "histo_col_type": "string", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"}, + {"num_eq": 100, "num_range": 100, "distinct_range": 10, "upper_bound": "banana"}, + {"num_eq": 100, "num_range": 100, "distinct_range": 10, "upper_bound": "cherry"}, + {"num_eq": 200, "num_range": 100, "distinct_range": 10, "upper_bound": "mango"}, + {"num_eq": 200, "num_range": 100, "distinct_range": 10, "upper_bound": "pineapple"} + ] + } +]' +---- + +# -------------------------------------------------- +# Q1 +# -------------------------------------------------- + +opt set=(optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE k IN (110, 120, 130, 140) AND i = 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.0200012 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + └── scan t@t_i_idx + ├── columns: k:1!null i:2!null + ├── constraint: /2/1 + │ ├── [/500/110 - /500/110] + │ ├── [/500/120 - /500/120] + │ ├── [/500/130 - /500/130] + │ └── [/500/140 - /500/140] + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.01 + ├── key: (1) + └── fd: ()-->(2) + +opt set=(optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE k IN (110, 120, 130, 140) AND i = 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.0200012 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + └── scan t@t_i_idx + ├── columns: k:1!null i:2!null + ├── constraint: /2/1 + │ ├── [/500/110 - /500/110] + │ ├── [/500/120 - /500/120] + │ ├── [/500/130 - /500/130] + │ └── [/500/140 - /500/140] + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.01 + ├── key: (1) + └── fd: ()-->(2) + +# -------------------------------------------------- +# Q2 +# -------------------------------------------------- + +opt set=(optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= + ├── cost: 18.0500006 + ├── key: (1) + ├── fd: (1)-->(2,3) + └── select + ├── columns: k:1!null i:2!null + ├── cardinality: [0 - 4] + ├── stats: [rows=8e-10, distinct(1)=8e-10, null(1)=0] + │ histogram(1)= 0 2e-10 0 2e-10 0 2e-10 0 2e-10 + │ <--- 100 --- 110 --- 120 --- 130 + ├── cost: 18.0400002 + ├── key: (1) + ├── fd: (1)-->(2) + ├── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/501/100 - ] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ histogram(2)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(2) + └── filters + └── k:1 IN (100, 110, 120, 130) [outer=(1), constraints=(/1: [/100 - /100] [/110 - /110] [/120 - /120] [/130 - /130]; tight)] + +opt set=(optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 +---- +select + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= + ├── cost: 24.21 + ├── key: (1) + ├── fd: (1)-->(2,3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/100 - /100] + │ │ ├── [/110 - /110] + │ │ ├── [/120 - /120] + │ │ └── [/130 - /130] + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=4, distinct(1)=4, null(1)=0] + │ │ histogram(1)= 0 1 0 1 0 1 0 1 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 24.15 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] + +# -------------------------------------------------- +# Q3 +# -------------------------------------------------- + +opt set=(optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 3] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ histogram(1)= + │ histogram(2)= + ├── cost: 18.0500006 + ├── key: (1) + ├── fd: (1)-->(2,3) + └── select + ├── columns: k:1!null i:2!null + ├── cardinality: [0 - 3] + ├── stats: [rows=4e-17, distinct(1)=4e-17, null(1)=0] + │ histogram(1)= + ├── cost: 18.0400002 + ├── key: (1) + ├── fd: (1)-->(2) + ├── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/501/410 - ] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ histogram(2)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(2) + └── filters + └── k:1 IN (410, 420, 430) [outer=(1), constraints=(/1: [/410 - /410] [/420 - /420] [/430 - /430]; tight)] + +opt set=(optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 +---- +select + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 3] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ histogram(1)= + │ histogram(2)= + ├── cost: 19.03 + ├── key: (1) + ├── fd: (1)-->(2,3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/410 - /410] + │ │ ├── [/420 - /420] + │ │ └── [/430 - /430] + │ ├── cardinality: [0 - 3] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0] + │ │ histogram(1)= + │ ├── cost: 19.01 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] + +# -------------------------------------------------- +# Q4 +# -------------------------------------------------- + +opt set=(optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i = 400 AND s < 'apple' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0, distinct(1-3)=2e-07, null(1-3)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700002 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=8e-10] + │ ├── cost: 18.0500002 + │ ├── key: (1) + │ ├── fd: (1)-->(2,3) + │ └── select + │ ├── columns: k:1!null s:3!null + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=8e-10, distinct(1)=8e-10, null(1)=0] + │ │ histogram(1)= 0 2e-10 0 2e-10 0 2e-10 0 2e-10 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 18.0400002 + │ ├── key: (1) + │ ├── fd: (1)-->(3) + │ ├── scan t@t_s_idx + │ │ ├── columns: k:1!null s:3!null + │ │ ├── constraint: /3/1: (/NULL - /'apple') + │ │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(3)=2e-07, null(3)=0] + │ │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ │ histogram(3)= + │ │ ├── cost: 18.0200002 + │ │ ├── key: (1) + │ │ └── fd: (1)-->(3) + │ └── filters + │ └── k:1 IN (100, 110, 120, 130) [outer=(1), constraints=(/1: [/100 - /100] [/110 - /110] [/120 - /120] [/130 - /130]; tight)] + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + +opt set=(optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i = 400 AND s < 'apple' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0, distinct(1-3)=2e-07, null(1-3)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 24.22 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/100 - /100] + │ │ ├── [/110 - /110] + │ │ ├── [/120 - /120] + │ │ └── [/130 - /130] + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=4, distinct(1)=4, null(1)=0] + │ │ histogram(1)= 0 1 0 1 0 1 0 1 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 24.15 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + ├── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + └── s:3 < 'apple' [outer=(3), constraints=(/3: (/NULL - /'apple'); tight)] + +# -------------------------------------------------- +# Q5 +# -------------------------------------------------- + +opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE i = 400 AND s > 'z' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── stats: [rows=2e-07, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0] + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700014 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── stats: [rows=2e-07] + │ ├── cost: 18.0400014 + │ ├── key: (1) + │ ├── fd: (1)-->(2,3) + │ └── scan t@t_s_idx + │ ├── columns: k:1!null s:3!null + │ ├── constraint: /3/1: [/e'z\x00' - ] + │ ├── stats: [rows=2e-07, distinct(3)=2e-07, null(3)=0] + │ │ histogram(3)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(3) + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + +opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE i = 400 AND s > 'z' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── stats: [rows=2e-07, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0] + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700014 + ├── cost-flags: unbounded-cardinality + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── stats: [rows=2e-07] + │ ├── cost: 18.0400014 + │ ├── cost-flags: unbounded-cardinality + │ ├── key: (1) + │ ├── fd: (1)-->(2,3) + │ └── scan t@t_s_idx + │ ├── columns: k:1!null s:3!null + │ ├── constraint: /3/1: [/e'z\x00' - ] + │ ├── stats: [rows=2e-07, distinct(3)=2e-07, null(3)=0] + │ │ histogram(3)= + │ ├── cost: 18.0200002 + │ ├── cost-flags: unbounded-cardinality + │ ├── key: (1) + │ └── fd: (1)-->(3) + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + +# -------------------------------------------------- +# Q6 +# -------------------------------------------------- + +opt set=(optimizer_prefer_bounded_cardinality=false) +SELECT * FROM t WHERE k = 100 AND i = 500 AND s = 'zzz' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 1] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0] + │ histogram(1)= 0 2e-07 + │ <--- 100 + │ histogram(2)= + │ histogram(3)= + ├── cost: 9.04000121 + ├── key: () + ├── fd: ()-->(1-3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07] + │ ├── cost: 9.02000121 + │ ├── key: () + │ ├── fd: ()-->(1-3) + │ └── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/500/100 - /500/100] + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ │ histogram(1)= 0 2e-07 + │ │ <--- 100 + │ │ histogram(2)= + │ ├── cost: 9.01 + │ ├── key: () + │ └── fd: ()-->(1,2) + └── filters + └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] + +opt set=(optimizer_prefer_bounded_cardinality=true) +SELECT * FROM t WHERE k = 100 AND i = 500 AND s = 'zzz' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 1] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0] + │ histogram(1)= 0 2e-07 + │ <--- 100 + │ histogram(2)= + │ histogram(3)= + ├── cost: 9.04000121 + ├── key: () + ├── fd: ()-->(1-3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07] + │ ├── cost: 9.02000121 + │ ├── key: () + │ ├── fd: ()-->(1-3) + │ └── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/500/100 - /500/100] + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ │ histogram(1)= 0 2e-07 + │ │ <--- 100 + │ │ histogram(2)= + │ ├── cost: 9.01 + │ ├── key: () + │ └── fd: ()-->(1,2) + └── filters + └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] diff --git a/pkg/sql/prepared_stmt.go b/pkg/sql/prepared_stmt.go index d506000dc31c..903df6f788ab 100644 --- a/pkg/sql/prepared_stmt.go +++ b/pkg/sql/prepared_stmt.go @@ -164,6 +164,11 @@ func (p *planCosts) NumCustom() int { // AvgCustom returns the average cost of all the custom plan costs in planCosts. // If there are no custom plan costs, it returns 0. +// +// TODO(mgartner): Figure out how this should incorporate cost flags. Some of +// them, like UnboundedCardinality, are only set if session settings are set. +// When those session settings change, do we need to clear and recompute the +// average cost of custom plans? func (p *planCosts) AvgCustom() memo.Cost { if p.custom.length == 0 { return memo.Cost{C: 0} diff --git a/pkg/sql/sessiondatapb/local_only_session_data.proto b/pkg/sql/sessiondatapb/local_only_session_data.proto index c2ae4314121e..6684712dc763 100644 --- a/pkg/sql/sessiondatapb/local_only_session_data.proto +++ b/pkg/sql/sessiondatapb/local_only_session_data.proto @@ -567,6 +567,10 @@ message LocalOnlySessionData { // mix-typed comparisons with VARCHAR types. See #137837, #133037, and // #132268. bool legacy_varchar_typing = 150; + // OptimizerPreferBoundedCardinality instructs the optimizer to prefer query + // plans in which every expression has a bounded cardinality over plans with + // one or more expressions with unbounded cardinality. + bool optimizer_prefer_bounded_cardinality = 154; /////////////////////////////////////////////////////////////////////////// // WARNING: consider whether a session parameter you're adding needs to // diff --git a/pkg/sql/vars.go b/pkg/sql/vars.go index 4f7b4b072424..db721959e7b8 100644 --- a/pkg/sql/vars.go +++ b/pkg/sql/vars.go @@ -3583,6 +3583,23 @@ var varGen = map[string]sessionVar{ }, GlobalDefault: globalTrue, }, + + // CockroachDB extension. + `optimizer_prefer_bounded_cardinality`: { + GetStringVal: makePostgresBoolGetStringValFn(`optimizer_prefer_bounded_cardinality`), + Set: func(_ context.Context, m sessionDataMutator, s string) error { + b, err := paramparse.ParseBoolVar("optimizer_prefer_bounded_cardinality", s) + if err != nil { + return err + } + m.SetOptimizerPreferBoundedCardinality(b) + return nil + }, + Get: func(evalCtx *extendedEvalContext, _ *kv.Txn) (string, error) { + return formatBoolAsPostgresSetting(evalCtx.SessionData().OptimizerPreferBoundedCardinality), nil + }, + GlobalDefault: globalFalse, + }, } func ReplicationModeFromString(s string) (sessiondatapb.ReplicationMode, error) {