-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
*: Selection can control the conditions of the below scan plan. #2834
Conversation
Good Job! |
executor/executor.go
Outdated
// SelectionExec represents a filter executor. | ||
type SelectionExec struct { | ||
Src Executor | ||
Condition expression.Expression | ||
ctx context.Context | ||
schema *expression.Schema | ||
|
||
scanController bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comments for these two fields.
executor/executor.go
Outdated
} | ||
var conds []expression.Expression | ||
x.where, _, conds = plan.ExpressionsToPB(sc, tblFilters, client) | ||
e.Condition = expression.ComposeCNFCondition(e.ctx, conds...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.Condition don't need to be changed.
executor/executor.go
Outdated
@@ -461,21 +461,104 @@ func (e *TableDualExec) Close() error { | |||
return nil | |||
} | |||
|
|||
func convertCorCol2Constant(expr expression.Expression) (expression.Expression, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
convert-> substitute
executor/executor.go
Outdated
} | ||
newArgs = append(newArgs, newArg) | ||
} | ||
newSf, _ := expression.NewFunction(x.GetCtx(), x.FuncName.L, x.GetType(), newArgs...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you may consider the case of "Cast"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will look into this tonight.
executor/executor.go
Outdated
newSf, _ := expression.NewFunction(x.GetCtx(), x.FuncName.L, x.GetType(), newArgs...) | ||
return newSf, nil | ||
case *expression.CorrelatedColumn: | ||
val, err := x.Eval(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use the x.Data directly, without considering err
plan/expr_to_pb.go
Outdated
@@ -25,7 +25,8 @@ import ( | |||
"github.com/pingcap/tipb/go-tipb" | |||
) | |||
|
|||
func expressionsToPB(sc *variable.StatementContext, exprs []expression.Expression, client kv.Client) (pbExpr *tipb.Expr, pushed []expression.Expression, remained []expression.Expression) { | |||
// ExpressionsToPB change expression to tipb.Expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add .
plan/expr_to_pb.go
Outdated
@@ -25,7 +25,8 @@ import ( | |||
"github.com/pingcap/tipb/go-tipb" | |||
) | |||
|
|||
func expressionsToPB(sc *variable.StatementContext, exprs []expression.Expression, client kv.Client) (pbExpr *tipb.Expr, pushed []expression.Expression, remained []expression.Expression) { | |||
// ExpressionsToPB change expression to tipb.Expr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change -> converts
} | ||
|
||
// Schema implements the Executor Schema interface. | ||
func (e *SelectionExec) Schema() *expression.Schema { | ||
return e.schema | ||
} | ||
|
||
// initController will init the conditions of the below scan executor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add more comments for this function.
plan/physical_plan_builder.go
Outdated
} | ||
} | ||
|
||
func (p *Selection) getUsableIndicesAndPk(ds *DataSource) ([]*model.IndexInfo, model.CIStr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment for this function.
executor/executor.go
Outdated
allConstant = allConstant && ok | ||
} | ||
if allConstant { | ||
val, _ := x.Eval(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error can't be ignored ...
executor/executor.go
Outdated
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
x.indexPlan.IndexConditionPBExpr, _, _ = plan.ExpressionsToPB(sc, e.idxFilterConditions, client) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When do we update the e.idxFilterConditions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.idxFilterCondtion
use the value of *Select.IdxConditions
directly, i think this don't need to update in executor phase.
executor/executor.go
Outdated
@@ -484,6 +567,9 @@ func (e *SelectionExec) Next() (*Row, error) { | |||
if srcRow == nil { | |||
return nil, nil | |||
} | |||
if e.Condition == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can remove this.
plan/logical_plans.go
Outdated
@@ -146,6 +146,13 @@ type Selection struct { | |||
|
|||
// onTable means if this selection's child is a table scan or index scan. | |||
onTable bool | |||
|
|||
// If ScanController is true, then the child of this selection is a scan, | |||
// which use pk or index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add .
plan/physical_plan_builder.go
Outdated
@@ -792,6 +1027,11 @@ func (p *Selection) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanI | |||
if info != nil { | |||
return info, nil | |||
} | |||
info = p.tryToBuildScanByKeyAndCorCol() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is very heavy. It may be called multiple times. I suggested that If 'flagDecorrelated' is true, check it in an other logic but in physical plan builder.
executor/executor.go
Outdated
switch x := expr.(type) { | ||
case *expression.ScalarFunction: | ||
allConstant := true | ||
newArgs := make([]expression.Expression, 0, len(x.GetArgs())) | ||
for _, arg := range x.GetArgs() { | ||
newArg, ok := substituteCorCol2Constant(arg) | ||
newArg, ok, err := substituteCorCol2Constant(arg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ok
is redundant. You can just check if newArg is constant
plan/physical_plan_builder.go
Outdated
TableAsName: p.TableAsName, | ||
OutOfOrder: true, | ||
DBName: p.DBName, | ||
physicalTableSource: physicalTableSource{client: p.ctx.GetClient()}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
client: client
plan/physical_plan_builder.go
Outdated
@@ -783,6 +785,261 @@ func (p *Union) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanInfo, | |||
return info, nil | |||
} | |||
|
|||
func buildTableScanByKeyAndCorCol(p *DataSource, pkName model.CIStr, fakeConds []expression.Expression, origConds []expression.Expression) (*physicalPlanInfo, []expression.Expression, []expression.Expression) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I seems we don't need to do like that. Building filter condition during runtime is more reasonable.
executor/builder.go
Outdated
schema: v.Schema(), | ||
ctx: b.ctx, | ||
scanController: v.ScanController, | ||
Conditions: v.Conditions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you store the conditions, you don't need to store condition any more. Use a for loop to check condition.
plan/physical_plan_builder.go
Outdated
// getUsableIndicesAndPk will simply check whether the pk or one index could used in this situation by | ||
// checking whether this index or pk is contained in one condition that has correlated column, | ||
// and whether this condition can be used as an access condition. | ||
func (p *Selection) getUsableIndicesAndPk(ds *DataSource) ([]*model.IndexInfo, model.CIStr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't peek all indices out, just ensure one index during plan building.
executor/executor.go
Outdated
newArgs := make([]expression.Expression, 0, len(x.GetArgs())) | ||
for _, arg := range x.GetArgs() { | ||
newArg, err := substituteCorCol2Constant(arg) | ||
_, ok := newArg.(*expression.Constant) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this after checking error.
plan/physical_plan_builder.go
Outdated
// The first return value is the new expression, the second is a bool value tell whether the below expression is all constant. | ||
// The Second is used for simplify the scalar function. | ||
// If the args of one scalar function are all constant, we will substitute it to constant. | ||
func substituteCorCol2Constant(cond expression.Expression) expression.Expression { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You implement this function for two times. Why not put this function to expression package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One is change correlated column to expression.One, another is eval the correlated column to Datum. I thought eval a correlated column when its Data is nothing doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract all cor columns and set them to one, then you can reuse this function.
executor/executor.go
Outdated
scanController bool | ||
controllerInit bool | ||
Conditions []expression.Expression | ||
usableIndices []*model.IndexInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove this from selection executor.
executor/executor.go
Outdated
} | ||
x.where = x.indexPlan.TableConditionPBExpr | ||
default: | ||
return errors.New("Error type of PhysicalPlan") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
print %T to tout put the plan type
plan/physical_plan_builder.go
Outdated
var newConds []expression.Expression | ||
for _, expr := range p.Conditions { | ||
cond := pushDownNot(expr.Clone(), false, nil) | ||
if !cond.IsCorrelated() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check this before push down not
plan/physical_plan_builder.go
Outdated
// checking whether this index or pk is contained in one condition that has correlated column, | ||
// and whether this condition can be used as an access condition. | ||
func (p *Selection) getUsableIndicesAndPk(ds *DataSource) ([]*model.IndexInfo, model.CIStr) { | ||
indices, _ := availableIndices(ds.indexHints, ds.tableInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ignore the includeTableScan
plan/physical_plan_builder.go
Outdated
} else { | ||
is.readOnly = true | ||
} | ||
is.AccessCondition, condsBackUp = DetachIndexScanConditions(condsBackUp, is) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to calculate access condtion, index conds and tbl conds here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calculating access condition is to choose which index is better by accessEqualCount and accessInAndEqCount.
Idx conds and tbl conds is used to make explain result clear, can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove idx and tbl conds. and their pb
plan/logical_plans.go
Outdated
// Since one selection may call convert2PhysicalScan many times. We extract the PkName and indices | ||
// used for scanController only once and store them to judge whether this selection can convert to | ||
// scanController mode. | ||
usefulPkName model.CIStr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need to store the usefulPkName and usefulIndices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't store the usefulPkName and usefulIndices. We will need to extract them one more time when build controller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, it seems not cost too much.
plan/logical_plans.go
Outdated
// scanController mode. | ||
usefulPkName model.CIStr | ||
usefulIndices []*model.IndexInfo | ||
extractedUsefulThing bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set a flag that we set controller when the flag is true. The flag is true only the selection has correlated column and its child is DataSource. You can check this during decorrelation.
plan/physical_plan_builder.go
Outdated
if chosenPlan == nil || chosenPlan.accessEqualCount < is.accessEqualCount || chosenPlan.accessInAndEqCount < is.accessInAndEqCount { | ||
chosenPlan = is | ||
} | ||
is.DoubleRead = isCoveringIndex(is.Columns, is.Index.Columns, is.Table.PKIsHandle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing !
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add test for it!
plan/decorrelate.go
Outdated
@@ -158,6 +160,11 @@ func (s *decorrelateSolver) optimize(p LogicalPlan, _ context.Context, _ *idAllo | |||
} | |||
} | |||
} | |||
if sel, ok := p.(*Selection); ok { | |||
if ds, ok := p.(*DataSource); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p.GetChildren()[0]...
output controller to sel explain result. and add explain test for it.
p: &newSel, | ||
count: uint64(ds.statisticTable.Count), | ||
} | ||
info.cost = float64(info.count) * selectionFactor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this cost calculation okay there?
expression/util.go
Outdated
return &Constant{Value: *x.Data, RetType: x.GetType()}, nil | ||
case *Constant: | ||
return x.Clone(), nil | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which case we will fall to default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*Column will fall to default. i will move *Constant to default too.
@winoros |
@winoros |
expression/util.go
Outdated
// If the args of one scalar function are all constant, we will substitute it to constant. | ||
// If it's called in plan phase, correlated column will change to expression.One. | ||
// If in executor phase, correlated column will change to value it contains. | ||
func SubstituteCorCol2Constant(expr Expression) (Expression, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not too hard to write a test for this function.
plan/decorrelate.go
Outdated
// hasUsableIndicesAndPk will simply check whether the pk or one index could used in this situation by | ||
// checking whether this index or pk is contained in one condition that has correlated column, | ||
// and whether this condition can be used as an access condition. | ||
func (p *Selection) hasUsableIndicesAndPk(ds *DataSource) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is very complex, need to write a test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
plan/decorrelate.go
Outdated
// If one cond is ok, then this index is useful. | ||
if checker.check(cond) { | ||
usable = true | ||
break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just return true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
} | ||
child = ts | ||
} else { | ||
var chosenPlan *PhysicalIndexScan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This branch is not covered.
availableIndices
returns all indices for you to chose if there is no index hint, but if the table has a PKHandle column, other indices will never be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added.
plan/decorrelate.go
Outdated
@@ -167,3 +173,64 @@ func (s *decorrelateSolver) optimize(p LogicalPlan, _ context.Context, _ *idAllo | |||
p.SetChildren(newChildren...) | |||
return p, nil | |||
} | |||
|
|||
// hasUsableIndicesAndPk will simply check whether the pk or one index could used in this situation by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be used
plan/decorrelate.go
Outdated
@@ -167,3 +173,64 @@ func (s *decorrelateSolver) optimize(p LogicalPlan, _ context.Context, _ *idAllo | |||
p.SetChildren(newChildren...) | |||
return p, nil | |||
} | |||
|
|||
// hasUsableIndicesAndPk will simply check whether the pk or one index could used in this situation by | |||
// checking whether this index or pk is contained in one condition that has correlated column, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in any condition
plan/decorrelate.go
Outdated
if ds.tableInfo.PKIsHandle && includeTableScan { | ||
var pkCol *expression.Column | ||
for i, col := range ds.Columns { | ||
if mysql.HasPriKeyFlag(col.Flag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider composed index?
plan/decorrelate.go
Outdated
if pkCol != nil { | ||
checker := conditionChecker{ | ||
pkName: pkCol.ColName, | ||
length: types.UnspecifiedLength, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use UnspecifiedLength?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same with DetachTableScanConditions() in refiner.go.
plan/physical_plan_builder.go
Outdated
@@ -981,6 +982,98 @@ func (p *Union) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanInfo, | |||
return info, nil | |||
} | |||
|
|||
func (p *Selection) makeScanController() *physicalPlanInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function could be merged with hasUsableIndicesAndPk.
PTAL @shenli |
LGTM |
If one of the conditions of this selection contains both key and correlated column and the child of it is a table scan, this condition could be pushed into the access conditions or filter conditions because the correlated column is constant when executed.
After this, we could change some join to index nested loop join.
PTAL @hanfei1991 @shenli @coocood @lamxTyler