-
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
Changes from 28 commits
5db58a9
1fe024d
5ce9107
364e172
36283cf
7451287
053c2d4
e663291
a25a3eb
6aadeaa
ba29a95
02cbcf1
9a9ae32
ef178a3
8029c1e
9771129
7251827
e45f669
4e0012b
f0ec2e0
02db63c
c791ff7
8674c3f
107a042
3fd0435
8f29d17
088e9e6
6351036
97998fc
55f92e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -473,15 +473,67 @@ type SelectionExec struct { | |
Condition expression.Expression | ||
ctx context.Context | ||
schema *expression.Schema | ||
|
||
// scanController will tell whether this selection need to | ||
// control the condition of below scan executor. | ||
scanController bool | ||
controllerInit bool | ||
Conditions []expression.Expression | ||
} | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. Add more comments for this function. |
||
// It will first substitute the correlated column to constant, then build range and filter by new conditions. | ||
func (e *SelectionExec) initController() error { | ||
sc := e.ctx.GetSessionVars().StmtCtx | ||
client := e.ctx.GetClient() | ||
newConds := make([]expression.Expression, 0, len(e.Conditions)) | ||
for _, cond := range e.Conditions { | ||
newCond, err := expression.SubstituteCorCol2Constant(cond.Clone()) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
newConds = append(newConds, newCond) | ||
} | ||
|
||
switch x := e.Src.(type) { | ||
case *XSelectTableExec: | ||
accessCondition, restCondtion := plan.DetachTableScanConditions(newConds, x.tableInfo) | ||
x.where, _, _ = plan.ExpressionsToPB(sc, restCondtion, client) | ||
ranges, err := plan.BuildTableRange(accessCondition, sc) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
x.ranges = ranges | ||
case *XSelectIndexExec: | ||
x.indexPlan.AccessCondition, newConds = plan.DetachIndexScanConditions(newConds, x.indexPlan) | ||
idxConds, tblConds := plan.DetachIndexFilterConditions(newConds, x.indexPlan.Index.Columns, x.indexPlan.Table) | ||
x.indexPlan.IndexConditionPBExpr, _, _ = plan.ExpressionsToPB(sc, idxConds, client) | ||
x.indexPlan.TableConditionPBExpr, _, _ = plan.ExpressionsToPB(sc, tblConds, client) | ||
err := plan.BuildIndexRange(sc, x.indexPlan) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
x.where = x.indexPlan.TableConditionPBExpr | ||
default: | ||
return errors.Errorf("Error type of Executor: %T", x) | ||
} | ||
return nil | ||
} | ||
|
||
// Next implements the Executor Next interface. | ||
func (e *SelectionExec) Next() (*Row, error) { | ||
if e.scanController && !e.controllerInit { | ||
err := e.initController() | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
e.controllerInit = true | ||
} | ||
for { | ||
srcRow, err := e.Src.Next() | ||
if err != nil { | ||
|
@@ -490,18 +542,28 @@ func (e *SelectionExec) Next() (*Row, error) { | |
if srcRow == nil { | ||
return nil, nil | ||
} | ||
match, err := expression.EvalBool(e.Condition, srcRow.Data, e.ctx) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
allMatch := true | ||
for _, cond := range e.Conditions { | ||
match, err := expression.EvalBool(cond, srcRow.Data, e.ctx) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
if !match { | ||
allMatch = false | ||
break | ||
} | ||
} | ||
if match { | ||
if allMatch { | ||
return srcRow, nil | ||
} | ||
} | ||
} | ||
|
||
// Close implements the Executor Close interface. | ||
func (e *SelectionExec) Close() error { | ||
if e.scanController { | ||
e.controllerInit = false | ||
} | ||
return e.Src.Close() | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -175,3 +175,40 @@ func (d *distinctChecker) Check(values []interface{}) (bool, error) { | |
d.existingKeys[key] = true | ||
return true, nil | ||
} | ||
|
||
// SubstituteCorCol2Constant will substitute correlated column to constant value which it contains. | ||
// If the args of one scalar function are all constant, we will substitute it to constant. | ||
func SubstituteCorCol2Constant(expr Expression) (Expression, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
switch x := expr.(type) { | ||
case *ScalarFunction: | ||
allConstant := true | ||
newArgs := make([]Expression, 0, len(x.GetArgs())) | ||
for _, arg := range x.GetArgs() { | ||
newArg, err := SubstituteCorCol2Constant(arg) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
_, ok := newArg.(*Constant) | ||
newArgs = append(newArgs, newArg) | ||
allConstant = allConstant && ok | ||
} | ||
if allConstant { | ||
val, err := x.Eval(nil) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
return &Constant{Value: val}, nil | ||
} | ||
var newSf Expression | ||
if x.FuncName.L == ast.Cast { | ||
newSf = NewCastFunc(x.RetType, newArgs[0], x.GetCtx()) | ||
} else { | ||
newSf, _ = NewFunction(x.GetCtx(), x.FuncName.L, x.GetType(), newArgs...) | ||
} | ||
return newSf, nil | ||
case *CorrelatedColumn: | ||
return &Constant{Value: *x.Data, RetType: x.GetType()}, nil | ||
default: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. *Column will fall to default. i will move *Constant to default too. |
||
return x.Clone(), nil | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ import ( | |
"github.com/pingcap/tidb/ast" | ||
"github.com/pingcap/tidb/context" | ||
"github.com/pingcap/tidb/expression" | ||
"github.com/pingcap/tidb/mysql" | ||
"github.com/pingcap/tidb/util/types" | ||
) | ||
|
||
|
@@ -158,6 +159,11 @@ func (s *decorrelateSolver) optimize(p LogicalPlan, _ context.Context, _ *idAllo | |
} | ||
} | ||
} | ||
if sel, ok := p.(*Selection); ok { | ||
if ds, ok := p.Children()[0].(*DataSource); ok { | ||
sel.canControlScan = sel.hasUsableIndicesAndPk(ds) | ||
} | ||
} | ||
newChildren := make([]Plan, 0, len(p.Children())) | ||
for _, child := range p.Children() { | ||
np, _ := s.optimize(child.(LogicalPlan), nil, nil) | ||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. could be used |
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. in any condition |
||
// 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
indices, includeTableScan := availableIndices(ds.indexHints, ds.tableInfo) | ||
var newConds []expression.Expression | ||
for _, expr := range p.Conditions { | ||
if !expr.IsCorrelated() { | ||
continue | ||
} | ||
cond := pushDownNot(expr, false, nil) | ||
corCols := extractCorColumns(cond) | ||
for _, col := range corCols { | ||
*col.Data = expression.One.Value | ||
} | ||
newCond, _ := expression.SubstituteCorCol2Constant(cond) | ||
newConds = append(newConds, newCond) | ||
} | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Consider composed index? |
||
pkCol = ds.schema.Columns[i] | ||
break | ||
} | ||
} | ||
if pkCol != nil { | ||
checker := conditionChecker{ | ||
pkName: pkCol.ColName, | ||
length: types.UnspecifiedLength, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. This is the same with DetachTableScanConditions() in refiner.go. |
||
} | ||
// Pk should satisfies the same property as the index. | ||
for _, cond := range newConds { | ||
if checker.check(cond) { | ||
return true | ||
} | ||
} | ||
} | ||
} | ||
for _, idx := range indices { | ||
// TODO: Currently we don't consider composite index. | ||
if len(idx.Columns) > 1 { | ||
continue | ||
} | ||
checker := &conditionChecker{ | ||
idx: idx, | ||
columnOffset: 0, | ||
length: idx.Columns[0].Length, | ||
} | ||
// This idx column should occur in one condition which contains both column and correlated column. | ||
// And conditionChecker.check(this condition) should be true. | ||
for _, cond := range newConds { | ||
// If one cond is ok, then this index is useful. | ||
if checker.check(cond) { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} |
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.