planner: move hint code to two separate code files (#49597)
ref pingcap/tidb#48875
This commit is contained in:
@ -18,6 +18,7 @@ go_library(
|
||||
"fragment.go",
|
||||
"handle_cols.go",
|
||||
"hashcode.go",
|
||||
"hint_utils.go",
|
||||
"hints.go",
|
||||
"indexmerge_path.go",
|
||||
"initialize.go",
|
||||
|
||||
416
pkg/planner/core/hint_utils.go
Normal file
416
pkg/planner/core/hint_utils.go
Normal file
@ -0,0 +1,416 @@
|
||||
// Copyright 2023 PingCAP, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pingcap/tidb/pkg/kv"
|
||||
"github.com/pingcap/tidb/pkg/parser/ast"
|
||||
"github.com/pingcap/tidb/pkg/parser/model"
|
||||
"github.com/pingcap/tidb/pkg/sessionctx"
|
||||
utilhint "github.com/pingcap/tidb/pkg/util/hint"
|
||||
)
|
||||
|
||||
// GenHintsFromFlatPlan generates hints from a FlatPhysicalPlan.
|
||||
func GenHintsFromFlatPlan(flat *FlatPhysicalPlan) []*ast.TableOptimizerHint {
|
||||
if len(flat.Main) == 0 {
|
||||
return nil
|
||||
}
|
||||
nodeTp := utilhint.TypeSelect
|
||||
switch flat.Main[0].Origin.(type) {
|
||||
case *Update:
|
||||
nodeTp = utilhint.TypeUpdate
|
||||
case *Delete:
|
||||
nodeTp = utilhint.TypeDelete
|
||||
}
|
||||
var hints []*ast.TableOptimizerHint
|
||||
selectPlan, _ := flat.Main.GetSelectPlan()
|
||||
if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan {
|
||||
return nil
|
||||
}
|
||||
for _, op := range selectPlan {
|
||||
p := op.Origin.(PhysicalPlan)
|
||||
hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints)
|
||||
}
|
||||
for _, cte := range flat.CTEs {
|
||||
for i, op := range cte {
|
||||
if i == 0 || !op.IsRoot {
|
||||
continue
|
||||
}
|
||||
p := op.Origin.(PhysicalPlan)
|
||||
hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints)
|
||||
}
|
||||
}
|
||||
return removeDuplicatedHints(hints)
|
||||
}
|
||||
|
||||
// GenHintsFromPhysicalPlan generates hints from physical plan.
|
||||
func GenHintsFromPhysicalPlan(p Plan) []*ast.TableOptimizerHint {
|
||||
flat := FlattenPhysicalPlan(p, false)
|
||||
return GenHintsFromFlatPlan(flat)
|
||||
}
|
||||
|
||||
func getTableName(tblName model.CIStr, asName *model.CIStr) model.CIStr {
|
||||
if asName != nil && asName.L != "" {
|
||||
return *asName
|
||||
}
|
||||
return tblName
|
||||
}
|
||||
|
||||
func extractTableAsName(p PhysicalPlan) (*model.CIStr, *model.CIStr) {
|
||||
if len(p.Children()) > 1 {
|
||||
return nil, nil
|
||||
}
|
||||
switch x := p.(type) {
|
||||
case *PhysicalTableReader:
|
||||
ts := x.TablePlans[0].(*PhysicalTableScan)
|
||||
if ts.TableAsName.L != "" {
|
||||
return &ts.DBName, ts.TableAsName
|
||||
}
|
||||
return &ts.DBName, &ts.Table.Name
|
||||
case *PhysicalIndexReader:
|
||||
is := x.IndexPlans[0].(*PhysicalIndexScan)
|
||||
if is.TableAsName.L != "" {
|
||||
return &is.DBName, is.TableAsName
|
||||
}
|
||||
return &is.DBName, &is.Table.Name
|
||||
case *PhysicalIndexLookUpReader:
|
||||
is := x.IndexPlans[0].(*PhysicalIndexScan)
|
||||
if is.TableAsName.L != "" {
|
||||
return &is.DBName, is.TableAsName
|
||||
}
|
||||
return &is.DBName, &is.Table.Name
|
||||
case *PhysicalSort, *PhysicalSelection, *PhysicalUnionScan, *PhysicalProjection:
|
||||
return extractTableAsName(p.Children()[0])
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getJoinHints(sctx sessionctx.Context, joinType string, parentOffset int, nodeType utilhint.NodeType, children ...PhysicalPlan) (res []*ast.TableOptimizerHint) {
|
||||
if parentOffset == -1 {
|
||||
return res
|
||||
}
|
||||
for _, child := range children {
|
||||
blockOffset := child.SelectBlockOffset()
|
||||
if blockOffset == -1 {
|
||||
continue
|
||||
}
|
||||
var dbName, tableName *model.CIStr
|
||||
if blockOffset != parentOffset {
|
||||
var blockAsNames []ast.HintTable
|
||||
if p := sctx.GetSessionVars().PlannerSelectBlockAsName.Load(); p != nil {
|
||||
blockAsNames = *p
|
||||
}
|
||||
if blockOffset >= len(blockAsNames) {
|
||||
continue
|
||||
}
|
||||
hintTable := blockAsNames[blockOffset]
|
||||
// For sub-queries like `(select * from t) t1`, t1 should belong to its surrounding select block.
|
||||
dbName, tableName, blockOffset = &hintTable.DBName, &hintTable.TableName, parentOffset
|
||||
} else {
|
||||
dbName, tableName = extractTableAsName(child)
|
||||
}
|
||||
if tableName == nil || tableName.L == "" {
|
||||
continue
|
||||
}
|
||||
qbName, err := utilhint.GenerateQBName(nodeType, blockOffset)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(joinType),
|
||||
Tables: []ast.HintTable{{DBName: *dbName, TableName: *tableName}},
|
||||
})
|
||||
break
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func genHintsFromSingle(p PhysicalPlan, nodeType utilhint.NodeType, storeType kv.StoreType, res []*ast.TableOptimizerHint) []*ast.TableOptimizerHint {
|
||||
qbName, err := utilhint.GenerateQBName(nodeType, p.SelectBlockOffset())
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
switch pp := p.(type) {
|
||||
case *PhysicalLimit, *PhysicalTopN:
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintLimitToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalTableReader:
|
||||
tbl, ok := pp.TablePlans[0].(*PhysicalTableScan)
|
||||
if !ok {
|
||||
return res
|
||||
}
|
||||
if tbl.StoreType == kv.TiFlash {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintReadFromStorage),
|
||||
HintData: model.NewCIStr(kv.TiFlash.Name()),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
})
|
||||
} else {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
})
|
||||
if tbl.Table.PKIsHandle || tbl.Table.IsCommonHandle { // it's a primary key
|
||||
orderHint := HintOrderIndex
|
||||
if !tbl.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
Indexes: []model.CIStr{model.NewCIStr("primary")},
|
||||
})
|
||||
}
|
||||
}
|
||||
case *PhysicalIndexLookUpReader:
|
||||
index := pp.IndexPlans[0].(*PhysicalIndexScan)
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
orderHint := HintOrderIndex
|
||||
if !index.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
case *PhysicalIndexReader:
|
||||
index := pp.IndexPlans[0].(*PhysicalIndexScan)
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
orderHint := HintOrderIndex
|
||||
if !index.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
case *PhysicalIndexMergeReader:
|
||||
indexs := make([]model.CIStr, 0, 2)
|
||||
var tableName model.CIStr
|
||||
var tableAsName *model.CIStr
|
||||
for _, partialPlan := range pp.PartialPlans {
|
||||
if index, ok := partialPlan[0].(*PhysicalIndexScan); ok {
|
||||
indexs = append(indexs, index.Index.Name)
|
||||
tableName = index.Table.Name
|
||||
tableAsName = index.TableAsName
|
||||
} else {
|
||||
indexName := model.NewCIStr("PRIMARY")
|
||||
indexs = append(indexs, indexName)
|
||||
}
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintIndexMerge),
|
||||
Tables: []ast.HintTable{{TableName: getTableName(tableName, tableAsName)}},
|
||||
Indexes: indexs,
|
||||
})
|
||||
case *PhysicalHashAgg:
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintHashAgg),
|
||||
})
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintAggToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalStreamAgg:
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintStreamAgg),
|
||||
})
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintAggToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalMergeJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintSMJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
|
||||
case *PhysicalHashJoin:
|
||||
// TODO: support the hash_join_build and hash_join_probe hint for auto capture
|
||||
res = append(res, getJoinHints(p.SCtx(), HintHJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
|
||||
case *PhysicalIndexJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
case *PhysicalIndexMergeJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLMJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
case *PhysicalIndexHashJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLHJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func removeDuplicatedHints(hints []*ast.TableOptimizerHint) []*ast.TableOptimizerHint {
|
||||
if len(hints) < 2 {
|
||||
return hints
|
||||
}
|
||||
m := make(map[string]struct{}, len(hints))
|
||||
res := make([]*ast.TableOptimizerHint, 0, len(hints))
|
||||
for _, hint := range hints {
|
||||
key := utilhint.RestoreTableOptimizerHint(hint)
|
||||
if _, ok := m[key]; ok {
|
||||
continue
|
||||
}
|
||||
m[key] = struct{}{}
|
||||
res = append(res, hint)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func tableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTables []ast.HintTable, p *utilhint.BlockHintProcessor, currentOffset int) []hintTableInfo {
|
||||
if len(hintTables) == 0 {
|
||||
return nil
|
||||
}
|
||||
hintTableInfos := make([]hintTableInfo, 0, len(hintTables))
|
||||
defaultDBName := model.NewCIStr(ctx.GetSessionVars().CurrentDB)
|
||||
isInapplicable := false
|
||||
for _, hintTable := range hintTables {
|
||||
tableInfo := hintTableInfo{
|
||||
dbName: hintTable.DBName,
|
||||
tblName: hintTable.TableName,
|
||||
partitions: hintTable.PartitionList,
|
||||
selectOffset: p.GetHintOffset(hintTable.QBName, currentOffset),
|
||||
}
|
||||
if tableInfo.dbName.L == "" {
|
||||
tableInfo.dbName = defaultDBName
|
||||
}
|
||||
switch hintName {
|
||||
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ, HintLeading:
|
||||
if len(tableInfo.partitions) > 0 {
|
||||
isInapplicable = true
|
||||
}
|
||||
}
|
||||
hintTableInfos = append(hintTableInfos, tableInfo)
|
||||
}
|
||||
if isInapplicable {
|
||||
ctx.GetSessionVars().StmtCtx.AppendWarning(
|
||||
fmt.Errorf("Optimizer Hint %s is inapplicable on specified partitions",
|
||||
restore2JoinHint(hintName, hintTableInfos)))
|
||||
return nil
|
||||
}
|
||||
return hintTableInfos
|
||||
}
|
||||
|
||||
func restore2TableHint(hintTables ...hintTableInfo) string {
|
||||
buffer := bytes.NewBufferString("")
|
||||
for i, table := range hintTables {
|
||||
buffer.WriteString(table.tblName.L)
|
||||
if len(table.partitions) > 0 {
|
||||
buffer.WriteString(" PARTITION(")
|
||||
for j, partition := range table.partitions {
|
||||
if j > 0 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
buffer.WriteString(partition.L)
|
||||
}
|
||||
buffer.WriteString(")")
|
||||
}
|
||||
if i < len(hintTables)-1 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2JoinHint(hintType string, hintTables []hintTableInfo) string {
|
||||
if len(hintTables) == 0 {
|
||||
return strings.ToUpper(hintType)
|
||||
}
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(hintType))
|
||||
buffer.WriteString("(")
|
||||
buffer.WriteString(restore2TableHint(hintTables...))
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2IndexHint(hintType string, hintIndex indexHintInfo) string {
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(hintType))
|
||||
buffer.WriteString("(")
|
||||
buffer.WriteString(restore2TableHint(hintTableInfo{
|
||||
dbName: hintIndex.dbName,
|
||||
tblName: hintIndex.tblName,
|
||||
partitions: hintIndex.partitions,
|
||||
}))
|
||||
if hintIndex.indexHint != nil && len(hintIndex.indexHint.IndexNames) > 0 {
|
||||
for i, indexName := range hintIndex.indexHint.IndexNames {
|
||||
if i > 0 {
|
||||
buffer.WriteString(",")
|
||||
}
|
||||
buffer.WriteString(" " + indexName.L)
|
||||
}
|
||||
}
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2StorageHint(tiflashTables, tikvTables []hintTableInfo) string {
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(HintReadFromStorage))
|
||||
buffer.WriteString("(")
|
||||
if len(tiflashTables) > 0 {
|
||||
buffer.WriteString("tiflash[")
|
||||
buffer.WriteString(restore2TableHint(tiflashTables...))
|
||||
buffer.WriteString("]")
|
||||
if len(tikvTables) > 0 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if len(tikvTables) > 0 {
|
||||
buffer.WriteString("tikv[")
|
||||
buffer.WriteString(restore2TableHint(tikvTables...))
|
||||
buffer.WriteString("]")
|
||||
}
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func extractUnmatchedTables(hintTables []hintTableInfo) []string {
|
||||
var tableNames []string
|
||||
for _, table := range hintTables {
|
||||
if !table.matched {
|
||||
tableNames = append(tableNames, table.tblName.O)
|
||||
}
|
||||
}
|
||||
return tableNames
|
||||
}
|
||||
@ -15,279 +15,312 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pingcap/tidb/pkg/kv"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pingcap/tidb/pkg/parser/ast"
|
||||
"github.com/pingcap/tidb/pkg/parser/model"
|
||||
"github.com/pingcap/tidb/pkg/sessionctx"
|
||||
utilhint "github.com/pingcap/tidb/pkg/util/hint"
|
||||
)
|
||||
|
||||
// GenHintsFromFlatPlan generates hints from a FlatPhysicalPlan.
|
||||
func GenHintsFromFlatPlan(flat *FlatPhysicalPlan) []*ast.TableOptimizerHint {
|
||||
if len(flat.Main) == 0 {
|
||||
// Hint flags listed here are used by PlanBuilder.subQueryHintFlags.
|
||||
const (
|
||||
// TiDBMergeJoin is hint enforce merge join.
|
||||
TiDBMergeJoin = "tidb_smj"
|
||||
// HintSMJ is hint enforce merge join.
|
||||
HintSMJ = "merge_join"
|
||||
// HintNoMergeJoin is the hint to enforce the query not to use merge join.
|
||||
HintNoMergeJoin = "no_merge_join"
|
||||
|
||||
// TiDBBroadCastJoin indicates applying broadcast join by force.
|
||||
TiDBBroadCastJoin = "tidb_bcj"
|
||||
// HintBCJ indicates applying broadcast join by force.
|
||||
HintBCJ = "broadcast_join"
|
||||
// HintShuffleJoin indicates applying shuffle join by force.
|
||||
HintShuffleJoin = "shuffle_join"
|
||||
|
||||
// HintStraightJoin causes TiDB to join tables in the order in which they appear in the FROM clause.
|
||||
HintStraightJoin = "straight_join"
|
||||
// HintLeading specifies the set of tables to be used as the prefix in the execution plan.
|
||||
HintLeading = "leading"
|
||||
|
||||
// TiDBIndexNestedLoopJoin is hint enforce index nested loop join.
|
||||
TiDBIndexNestedLoopJoin = "tidb_inlj"
|
||||
// HintINLJ is hint enforce index nested loop join.
|
||||
HintINLJ = "inl_join"
|
||||
// HintINLHJ is hint enforce index nested loop hash join.
|
||||
HintINLHJ = "inl_hash_join"
|
||||
// HintINLMJ is hint enforce index nested loop merge join.
|
||||
HintINLMJ = "inl_merge_join"
|
||||
// HintNoIndexJoin is the hint to enforce the query not to use index join.
|
||||
HintNoIndexJoin = "no_index_join"
|
||||
// HintNoIndexHashJoin is the hint to enforce the query not to use index hash join.
|
||||
HintNoIndexHashJoin = "no_index_hash_join"
|
||||
// HintNoIndexMergeJoin is the hint to enforce the query not to use index merge join.
|
||||
HintNoIndexMergeJoin = "no_index_merge_join"
|
||||
// TiDBHashJoin is hint enforce hash join.
|
||||
TiDBHashJoin = "tidb_hj"
|
||||
// HintNoHashJoin is the hint to enforce the query not to use hash join.
|
||||
HintNoHashJoin = "no_hash_join"
|
||||
// HintHJ is hint enforce hash join.
|
||||
HintHJ = "hash_join"
|
||||
// HintHashJoinBuild is hint enforce hash join's build side
|
||||
HintHashJoinBuild = "hash_join_build"
|
||||
// HintHashJoinProbe is hint enforce hash join's probe side
|
||||
HintHashJoinProbe = "hash_join_probe"
|
||||
// HintHashAgg is hint enforce hash aggregation.
|
||||
HintHashAgg = "hash_agg"
|
||||
// HintStreamAgg is hint enforce stream aggregation.
|
||||
HintStreamAgg = "stream_agg"
|
||||
// HintMPP1PhaseAgg enforces the optimizer to use the mpp-1phase aggregation.
|
||||
HintMPP1PhaseAgg = "mpp_1phase_agg"
|
||||
// HintMPP2PhaseAgg enforces the optimizer to use the mpp-2phase aggregation.
|
||||
HintMPP2PhaseAgg = "mpp_2phase_agg"
|
||||
// HintUseIndex is hint enforce using some indexes.
|
||||
HintUseIndex = "use_index"
|
||||
// HintIgnoreIndex is hint enforce ignoring some indexes.
|
||||
HintIgnoreIndex = "ignore_index"
|
||||
// HintForceIndex make optimizer to use this index even if it thinks a table scan is more efficient.
|
||||
HintForceIndex = "force_index"
|
||||
// HintOrderIndex is hint enforce using some indexes and keep the index's order.
|
||||
HintOrderIndex = "order_index"
|
||||
// HintNoOrderIndex is hint enforce using some indexes and not keep the index's order.
|
||||
HintNoOrderIndex = "no_order_index"
|
||||
// HintAggToCop is hint enforce pushing aggregation to coprocessor.
|
||||
HintAggToCop = "agg_to_cop"
|
||||
// HintReadFromStorage is hint enforce some tables read from specific type of storage.
|
||||
HintReadFromStorage = "read_from_storage"
|
||||
// HintTiFlash is a label represents the tiflash storage type.
|
||||
HintTiFlash = "tiflash"
|
||||
// HintTiKV is a label represents the tikv storage type.
|
||||
HintTiKV = "tikv"
|
||||
// HintIndexMerge is a hint to enforce using some indexes at the same time.
|
||||
HintIndexMerge = "use_index_merge"
|
||||
// HintTimeRange is a hint to specify the time range for metrics summary tables
|
||||
HintTimeRange = "time_range"
|
||||
// HintIgnorePlanCache is a hint to enforce ignoring plan cache
|
||||
HintIgnorePlanCache = "ignore_plan_cache"
|
||||
// HintLimitToCop is a hint enforce pushing limit or topn to coprocessor.
|
||||
HintLimitToCop = "limit_to_cop"
|
||||
// HintMerge is a hint which can switch turning inline for the CTE.
|
||||
HintMerge = "merge"
|
||||
// HintSemiJoinRewrite is a hint to force we rewrite the semi join operator as much as possible.
|
||||
HintSemiJoinRewrite = "semi_join_rewrite"
|
||||
// HintNoDecorrelate indicates a LogicalApply not to be decorrelated.
|
||||
HintNoDecorrelate = "no_decorrelate"
|
||||
|
||||
// HintMemoryQuota sets the memory limit for a query
|
||||
HintMemoryQuota = "memory_quota"
|
||||
// HintUseToja is a hint to optimize `in (select ...)` subquery into `join`
|
||||
HintUseToja = "use_toja"
|
||||
// HintNoIndexMerge is a hint to disable index merge
|
||||
HintNoIndexMerge = "no_index_merge"
|
||||
// HintMaxExecutionTime specifies the max allowed execution time in milliseconds
|
||||
HintMaxExecutionTime = "max_execution_time"
|
||||
|
||||
// HintFlagSemiJoinRewrite corresponds to HintSemiJoinRewrite.
|
||||
HintFlagSemiJoinRewrite uint64 = 1 << iota
|
||||
// HintFlagNoDecorrelate corresponds to HintNoDecorrelate.
|
||||
HintFlagNoDecorrelate
|
||||
)
|
||||
|
||||
type indexNestedLoopJoinTables struct {
|
||||
inljTables []hintTableInfo
|
||||
inlhjTables []hintTableInfo
|
||||
inlmjTables []hintTableInfo
|
||||
}
|
||||
|
||||
type tableHintInfo struct {
|
||||
indexNestedLoopJoinTables
|
||||
noIndexJoinTables indexNestedLoopJoinTables
|
||||
sortMergeJoinTables []hintTableInfo
|
||||
broadcastJoinTables []hintTableInfo
|
||||
shuffleJoinTables []hintTableInfo
|
||||
hashJoinTables []hintTableInfo
|
||||
noHashJoinTables []hintTableInfo
|
||||
noMergeJoinTables []hintTableInfo
|
||||
indexHintList []indexHintInfo
|
||||
tiflashTables []hintTableInfo
|
||||
tikvTables []hintTableInfo
|
||||
aggHints aggHintInfo
|
||||
indexMergeHintList []indexHintInfo
|
||||
timeRangeHint ast.HintTimeRange
|
||||
limitHints limitHintInfo
|
||||
MergeHints MergeHintInfo
|
||||
leadingJoinOrder []hintTableInfo
|
||||
hjBuildTables []hintTableInfo
|
||||
hjProbeTables []hintTableInfo
|
||||
}
|
||||
|
||||
type limitHintInfo struct {
|
||||
preferLimitToCop bool
|
||||
}
|
||||
|
||||
// MergeHintInfo ...one bool flag for cte
|
||||
type MergeHintInfo struct {
|
||||
preferMerge bool
|
||||
}
|
||||
|
||||
type hintTableInfo struct {
|
||||
dbName model.CIStr
|
||||
tblName model.CIStr
|
||||
partitions []model.CIStr
|
||||
selectOffset int
|
||||
matched bool
|
||||
}
|
||||
|
||||
type indexHintInfo struct {
|
||||
dbName model.CIStr
|
||||
tblName model.CIStr
|
||||
partitions []model.CIStr
|
||||
indexHint *ast.IndexHint
|
||||
// Matched indicates whether this index hint
|
||||
// has been successfully applied to a DataSource.
|
||||
// If an indexHintInfo is not matched after building
|
||||
// a Select statement, we will generate a warning for it.
|
||||
matched bool
|
||||
}
|
||||
|
||||
func (hint *indexHintInfo) match(dbName, tblName model.CIStr) bool {
|
||||
return hint.tblName.L == tblName.L &&
|
||||
(hint.dbName.L == dbName.L ||
|
||||
hint.dbName.L == "*") // for universal bindings, e.g. *.t
|
||||
}
|
||||
|
||||
func (hint *indexHintInfo) hintTypeString() string {
|
||||
switch hint.indexHint.HintType {
|
||||
case ast.HintUse:
|
||||
return "use_index"
|
||||
case ast.HintIgnore:
|
||||
return "ignore_index"
|
||||
case ast.HintForce:
|
||||
return "force_index"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// indexString formats the indexHint as dbName.tableName[, indexNames].
|
||||
func (hint *indexHintInfo) indexString() string {
|
||||
var indexListString string
|
||||
indexList := make([]string, len(hint.indexHint.IndexNames))
|
||||
for i := range hint.indexHint.IndexNames {
|
||||
indexList[i] = hint.indexHint.IndexNames[i].L
|
||||
}
|
||||
if len(indexList) > 0 {
|
||||
indexListString = fmt.Sprintf(", %s", strings.Join(indexList, ", "))
|
||||
}
|
||||
return fmt.Sprintf("%s.%s%s", hint.dbName, hint.tblName, indexListString)
|
||||
}
|
||||
|
||||
type aggHintInfo struct {
|
||||
preferAggType uint
|
||||
preferAggToCop bool
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.sortMergeJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferBroadcastJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.broadcastJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferShuffleJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.shuffleJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hashJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noHashJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noMergeJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hjBuildTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHJProbe(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hjProbeTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLHJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlhjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLMJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlmjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inljTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inlhjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inlmjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferTiFlash(tableName *hintTableInfo) *hintTableInfo {
|
||||
if tableName == nil {
|
||||
return nil
|
||||
}
|
||||
nodeTp := utilhint.TypeSelect
|
||||
switch flat.Main[0].Origin.(type) {
|
||||
case *Update:
|
||||
nodeTp = utilhint.TypeUpdate
|
||||
case *Delete:
|
||||
nodeTp = utilhint.TypeDelete
|
||||
for i, tbl := range info.tiflashTables {
|
||||
if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset {
|
||||
info.tiflashTables[i].matched = true
|
||||
return &tbl
|
||||
}
|
||||
}
|
||||
var hints []*ast.TableOptimizerHint
|
||||
selectPlan, _ := flat.Main.GetSelectPlan()
|
||||
if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferTiKV(tableName *hintTableInfo) *hintTableInfo {
|
||||
if tableName == nil {
|
||||
return nil
|
||||
}
|
||||
for _, op := range selectPlan {
|
||||
p := op.Origin.(PhysicalPlan)
|
||||
hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints)
|
||||
for i, tbl := range info.tikvTables {
|
||||
if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset {
|
||||
info.tikvTables[i].matched = true
|
||||
return &tbl
|
||||
}
|
||||
}
|
||||
for _, cte := range flat.CTEs {
|
||||
for i, op := range cte {
|
||||
if i == 0 || !op.IsRoot {
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchTableName checks whether the hint hit the need.
|
||||
// Only need either side matches one on the list.
|
||||
// Even though you can put 2 tables on the list,
|
||||
// it doesn't mean optimizer will reorder to make them
|
||||
// join directly.
|
||||
// Which it joins on with depend on sequence of traverse
|
||||
// and without reorder, user might adjust themselves.
|
||||
// This is similar to MySQL hints.
|
||||
func (*tableHintInfo) matchTableName(tables []*hintTableInfo, hintTables []hintTableInfo) bool {
|
||||
hintMatched := false
|
||||
for _, table := range tables {
|
||||
for i, curEntry := range hintTables {
|
||||
if table == nil {
|
||||
continue
|
||||
}
|
||||
p := op.Origin.(PhysicalPlan)
|
||||
hints = genHintsFromSingle(p, nodeTp, op.StoreType, hints)
|
||||
}
|
||||
}
|
||||
return removeDuplicatedHints(hints)
|
||||
}
|
||||
|
||||
// GenHintsFromPhysicalPlan generates hints from physical plan.
|
||||
func GenHintsFromPhysicalPlan(p Plan) []*ast.TableOptimizerHint {
|
||||
flat := FlattenPhysicalPlan(p, false)
|
||||
return GenHintsFromFlatPlan(flat)
|
||||
}
|
||||
|
||||
func getTableName(tblName model.CIStr, asName *model.CIStr) model.CIStr {
|
||||
if asName != nil && asName.L != "" {
|
||||
return *asName
|
||||
}
|
||||
return tblName
|
||||
}
|
||||
|
||||
func extractTableAsName(p PhysicalPlan) (*model.CIStr, *model.CIStr) {
|
||||
if len(p.Children()) > 1 {
|
||||
return nil, nil
|
||||
}
|
||||
switch x := p.(type) {
|
||||
case *PhysicalTableReader:
|
||||
ts := x.TablePlans[0].(*PhysicalTableScan)
|
||||
if ts.TableAsName.L != "" {
|
||||
return &ts.DBName, ts.TableAsName
|
||||
}
|
||||
return &ts.DBName, &ts.Table.Name
|
||||
case *PhysicalIndexReader:
|
||||
is := x.IndexPlans[0].(*PhysicalIndexScan)
|
||||
if is.TableAsName.L != "" {
|
||||
return &is.DBName, is.TableAsName
|
||||
}
|
||||
return &is.DBName, &is.Table.Name
|
||||
case *PhysicalIndexLookUpReader:
|
||||
is := x.IndexPlans[0].(*PhysicalIndexScan)
|
||||
if is.TableAsName.L != "" {
|
||||
return &is.DBName, is.TableAsName
|
||||
}
|
||||
return &is.DBName, &is.Table.Name
|
||||
case *PhysicalSort, *PhysicalSelection, *PhysicalUnionScan, *PhysicalProjection:
|
||||
return extractTableAsName(p.Children()[0])
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getJoinHints(sctx sessionctx.Context, joinType string, parentOffset int, nodeType utilhint.NodeType, children ...PhysicalPlan) (res []*ast.TableOptimizerHint) {
|
||||
if parentOffset == -1 {
|
||||
return res
|
||||
}
|
||||
for _, child := range children {
|
||||
blockOffset := child.SelectBlockOffset()
|
||||
if blockOffset == -1 {
|
||||
continue
|
||||
}
|
||||
var dbName, tableName *model.CIStr
|
||||
if blockOffset != parentOffset {
|
||||
var blockAsNames []ast.HintTable
|
||||
if p := sctx.GetSessionVars().PlannerSelectBlockAsName.Load(); p != nil {
|
||||
blockAsNames = *p
|
||||
}
|
||||
if blockOffset >= len(blockAsNames) {
|
||||
continue
|
||||
}
|
||||
hintTable := blockAsNames[blockOffset]
|
||||
// For sub-queries like `(select * from t) t1`, t1 should belong to its surrounding select block.
|
||||
dbName, tableName, blockOffset = &hintTable.DBName, &hintTable.TableName, parentOffset
|
||||
} else {
|
||||
dbName, tableName = extractTableAsName(child)
|
||||
}
|
||||
if tableName == nil || tableName.L == "" {
|
||||
continue
|
||||
}
|
||||
qbName, err := utilhint.GenerateQBName(nodeType, blockOffset)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(joinType),
|
||||
Tables: []ast.HintTable{{DBName: *dbName, TableName: *tableName}},
|
||||
})
|
||||
break
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func genHintsFromSingle(p PhysicalPlan, nodeType utilhint.NodeType, storeType kv.StoreType, res []*ast.TableOptimizerHint) []*ast.TableOptimizerHint {
|
||||
qbName, err := utilhint.GenerateQBName(nodeType, p.SelectBlockOffset())
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
switch pp := p.(type) {
|
||||
case *PhysicalLimit, *PhysicalTopN:
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintLimitToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalTableReader:
|
||||
tbl, ok := pp.TablePlans[0].(*PhysicalTableScan)
|
||||
if !ok {
|
||||
return res
|
||||
}
|
||||
if tbl.StoreType == kv.TiFlash {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintReadFromStorage),
|
||||
HintData: model.NewCIStr(kv.TiFlash.Name()),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
})
|
||||
} else {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
})
|
||||
if tbl.Table.PKIsHandle || tbl.Table.IsCommonHandle { // it's a primary key
|
||||
orderHint := HintOrderIndex
|
||||
if !tbl.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: tbl.DBName, TableName: getTableName(tbl.Table.Name, tbl.TableAsName)}},
|
||||
Indexes: []model.CIStr{model.NewCIStr("primary")},
|
||||
})
|
||||
if (curEntry.dbName.L == table.dbName.L || curEntry.dbName.L == "*") &&
|
||||
curEntry.tblName.L == table.tblName.L &&
|
||||
table.selectOffset == curEntry.selectOffset {
|
||||
hintTables[i].matched = true
|
||||
hintMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
case *PhysicalIndexLookUpReader:
|
||||
index := pp.IndexPlans[0].(*PhysicalIndexScan)
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
orderHint := HintOrderIndex
|
||||
if !index.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
case *PhysicalIndexReader:
|
||||
index := pp.IndexPlans[0].(*PhysicalIndexScan)
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintUseIndex),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
orderHint := HintOrderIndex
|
||||
if !index.KeepOrder {
|
||||
orderHint = HintNoOrderIndex
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(orderHint),
|
||||
Tables: []ast.HintTable{{DBName: index.DBName, TableName: getTableName(index.Table.Name, index.TableAsName)}},
|
||||
Indexes: []model.CIStr{index.Index.Name},
|
||||
})
|
||||
case *PhysicalIndexMergeReader:
|
||||
indexs := make([]model.CIStr, 0, 2)
|
||||
var tableName model.CIStr
|
||||
var tableAsName *model.CIStr
|
||||
for _, partialPlan := range pp.PartialPlans {
|
||||
if index, ok := partialPlan[0].(*PhysicalIndexScan); ok {
|
||||
indexs = append(indexs, index.Index.Name)
|
||||
tableName = index.Table.Name
|
||||
tableAsName = index.TableAsName
|
||||
} else {
|
||||
indexName := model.NewCIStr("PRIMARY")
|
||||
indexs = append(indexs, indexName)
|
||||
}
|
||||
}
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintIndexMerge),
|
||||
Tables: []ast.HintTable{{TableName: getTableName(tableName, tableAsName)}},
|
||||
Indexes: indexs,
|
||||
})
|
||||
case *PhysicalHashAgg:
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintHashAgg),
|
||||
})
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintAggToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalStreamAgg:
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintStreamAgg),
|
||||
})
|
||||
if storeType == kv.TiKV {
|
||||
res = append(res, &ast.TableOptimizerHint{
|
||||
QBName: qbName,
|
||||
HintName: model.NewCIStr(HintAggToCop),
|
||||
})
|
||||
}
|
||||
case *PhysicalMergeJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintSMJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
|
||||
case *PhysicalHashJoin:
|
||||
// TODO: support the hash_join_build and hash_join_probe hint for auto capture
|
||||
res = append(res, getJoinHints(p.SCtx(), HintHJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
|
||||
case *PhysicalIndexJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
case *PhysicalIndexMergeJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLMJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
case *PhysicalIndexHashJoin:
|
||||
res = append(res, getJoinHints(p.SCtx(), HintINLHJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func removeDuplicatedHints(hints []*ast.TableOptimizerHint) []*ast.TableOptimizerHint {
|
||||
if len(hints) < 2 {
|
||||
return hints
|
||||
}
|
||||
m := make(map[string]struct{}, len(hints))
|
||||
res := make([]*ast.TableOptimizerHint, 0, len(hints))
|
||||
for _, hint := range hints {
|
||||
key := utilhint.RestoreTableOptimizerHint(hint)
|
||||
if _, ok := m[key]; ok {
|
||||
continue
|
||||
}
|
||||
m[key] = struct{}{}
|
||||
res = append(res, hint)
|
||||
}
|
||||
return res
|
||||
return hintMatched
|
||||
}
|
||||
|
||||
@ -69,101 +69,6 @@ import (
|
||||
"github.com/pingcap/tipb/go-tipb"
|
||||
)
|
||||
|
||||
const (
|
||||
// TiDBMergeJoin is hint enforce merge join.
|
||||
TiDBMergeJoin = "tidb_smj"
|
||||
// HintSMJ is hint enforce merge join.
|
||||
HintSMJ = "merge_join"
|
||||
// HintNoMergeJoin is the hint to enforce the query not to use merge join.
|
||||
HintNoMergeJoin = "no_merge_join"
|
||||
|
||||
// TiDBBroadCastJoin indicates applying broadcast join by force.
|
||||
TiDBBroadCastJoin = "tidb_bcj"
|
||||
// HintBCJ indicates applying broadcast join by force.
|
||||
HintBCJ = "broadcast_join"
|
||||
// HintShuffleJoin indicates applying shuffle join by force.
|
||||
HintShuffleJoin = "shuffle_join"
|
||||
|
||||
// HintStraightJoin causes TiDB to join tables in the order in which they appear in the FROM clause.
|
||||
HintStraightJoin = "straight_join"
|
||||
// HintLeading specifies the set of tables to be used as the prefix in the execution plan.
|
||||
HintLeading = "leading"
|
||||
|
||||
// TiDBIndexNestedLoopJoin is hint enforce index nested loop join.
|
||||
TiDBIndexNestedLoopJoin = "tidb_inlj"
|
||||
// HintINLJ is hint enforce index nested loop join.
|
||||
HintINLJ = "inl_join"
|
||||
// HintINLHJ is hint enforce index nested loop hash join.
|
||||
HintINLHJ = "inl_hash_join"
|
||||
// HintINLMJ is hint enforce index nested loop merge join.
|
||||
HintINLMJ = "inl_merge_join"
|
||||
// HintNoIndexJoin is the hint to enforce the query not to use index join.
|
||||
HintNoIndexJoin = "no_index_join"
|
||||
// HintNoIndexHashJoin is the hint to enforce the query not to use index hash join.
|
||||
HintNoIndexHashJoin = "no_index_hash_join"
|
||||
// HintNoIndexMergeJoin is the hint to enforce the query not to use index merge join.
|
||||
HintNoIndexMergeJoin = "no_index_merge_join"
|
||||
// TiDBHashJoin is hint enforce hash join.
|
||||
TiDBHashJoin = "tidb_hj"
|
||||
// HintNoHashJoin is the hint to enforce the query not to use hash join.
|
||||
HintNoHashJoin = "no_hash_join"
|
||||
// HintHJ is hint enforce hash join.
|
||||
HintHJ = "hash_join"
|
||||
// HintHashJoinBuild is hint enforce hash join's build side
|
||||
HintHashJoinBuild = "hash_join_build"
|
||||
// HintHashJoinProbe is hint enforce hash join's probe side
|
||||
HintHashJoinProbe = "hash_join_probe"
|
||||
// HintHashAgg is hint enforce hash aggregation.
|
||||
HintHashAgg = "hash_agg"
|
||||
// HintStreamAgg is hint enforce stream aggregation.
|
||||
HintStreamAgg = "stream_agg"
|
||||
// HintMPP1PhaseAgg enforces the optimizer to use the mpp-1phase aggregation.
|
||||
HintMPP1PhaseAgg = "mpp_1phase_agg"
|
||||
// HintMPP2PhaseAgg enforces the optimizer to use the mpp-2phase aggregation.
|
||||
HintMPP2PhaseAgg = "mpp_2phase_agg"
|
||||
// HintUseIndex is hint enforce using some indexes.
|
||||
HintUseIndex = "use_index"
|
||||
// HintIgnoreIndex is hint enforce ignoring some indexes.
|
||||
HintIgnoreIndex = "ignore_index"
|
||||
// HintForceIndex make optimizer to use this index even if it thinks a table scan is more efficient.
|
||||
HintForceIndex = "force_index"
|
||||
// HintOrderIndex is hint enforce using some indexes and keep the index's order.
|
||||
HintOrderIndex = "order_index"
|
||||
// HintNoOrderIndex is hint enforce using some indexes and not keep the index's order.
|
||||
HintNoOrderIndex = "no_order_index"
|
||||
// HintAggToCop is hint enforce pushing aggregation to coprocessor.
|
||||
HintAggToCop = "agg_to_cop"
|
||||
// HintReadFromStorage is hint enforce some tables read from specific type of storage.
|
||||
HintReadFromStorage = "read_from_storage"
|
||||
// HintTiFlash is a label represents the tiflash storage type.
|
||||
HintTiFlash = "tiflash"
|
||||
// HintTiKV is a label represents the tikv storage type.
|
||||
HintTiKV = "tikv"
|
||||
// HintIndexMerge is a hint to enforce using some indexes at the same time.
|
||||
HintIndexMerge = "use_index_merge"
|
||||
// HintTimeRange is a hint to specify the time range for metrics summary tables
|
||||
HintTimeRange = "time_range"
|
||||
// HintIgnorePlanCache is a hint to enforce ignoring plan cache
|
||||
HintIgnorePlanCache = "ignore_plan_cache"
|
||||
// HintLimitToCop is a hint enforce pushing limit or topn to coprocessor.
|
||||
HintLimitToCop = "limit_to_cop"
|
||||
// HintMerge is a hint which can switch turning inline for the CTE.
|
||||
HintMerge = "merge"
|
||||
// HintSemiJoinRewrite is a hint to force we rewrite the semi join operator as much as possible.
|
||||
HintSemiJoinRewrite = "semi_join_rewrite"
|
||||
// HintNoDecorrelate indicates a LogicalApply not to be decorrelated.
|
||||
HintNoDecorrelate = "no_decorrelate"
|
||||
|
||||
// HintMemoryQuota sets the memory limit for a query
|
||||
HintMemoryQuota = "memory_quota"
|
||||
// HintUseToja is a hint to optimize `in (select ...)` subquery into `join`
|
||||
HintUseToja = "use_toja"
|
||||
// HintNoIndexMerge is a hint to disable index merge
|
||||
HintNoIndexMerge = "no_index_merge"
|
||||
// HintMaxExecutionTime specifies the max allowed execution time in milliseconds
|
||||
HintMaxExecutionTime = "max_execution_time"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrExprInSelect is in select fields for the error of ErrFieldNotInGroupBy
|
||||
ErrExprInSelect = "SELECT list"
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
@ -82,99 +81,6 @@ type visitInfo struct {
|
||||
dynamicWithGrant bool
|
||||
}
|
||||
|
||||
type indexNestedLoopJoinTables struct {
|
||||
inljTables []hintTableInfo
|
||||
inlhjTables []hintTableInfo
|
||||
inlmjTables []hintTableInfo
|
||||
}
|
||||
|
||||
type tableHintInfo struct {
|
||||
indexNestedLoopJoinTables
|
||||
noIndexJoinTables indexNestedLoopJoinTables
|
||||
sortMergeJoinTables []hintTableInfo
|
||||
broadcastJoinTables []hintTableInfo
|
||||
shuffleJoinTables []hintTableInfo
|
||||
hashJoinTables []hintTableInfo
|
||||
noHashJoinTables []hintTableInfo
|
||||
noMergeJoinTables []hintTableInfo
|
||||
indexHintList []indexHintInfo
|
||||
tiflashTables []hintTableInfo
|
||||
tikvTables []hintTableInfo
|
||||
aggHints aggHintInfo
|
||||
indexMergeHintList []indexHintInfo
|
||||
timeRangeHint ast.HintTimeRange
|
||||
limitHints limitHintInfo
|
||||
MergeHints MergeHintInfo
|
||||
leadingJoinOrder []hintTableInfo
|
||||
hjBuildTables []hintTableInfo
|
||||
hjProbeTables []hintTableInfo
|
||||
}
|
||||
|
||||
type limitHintInfo struct {
|
||||
preferLimitToCop bool
|
||||
}
|
||||
|
||||
// MergeHintInfo ...one bool flag for cte
|
||||
type MergeHintInfo struct {
|
||||
preferMerge bool
|
||||
}
|
||||
|
||||
type hintTableInfo struct {
|
||||
dbName model.CIStr
|
||||
tblName model.CIStr
|
||||
partitions []model.CIStr
|
||||
selectOffset int
|
||||
matched bool
|
||||
}
|
||||
|
||||
type indexHintInfo struct {
|
||||
dbName model.CIStr
|
||||
tblName model.CIStr
|
||||
partitions []model.CIStr
|
||||
indexHint *ast.IndexHint
|
||||
// Matched indicates whether this index hint
|
||||
// has been successfully applied to a DataSource.
|
||||
// If an indexHintInfo is not matched after building
|
||||
// a Select statement, we will generate a warning for it.
|
||||
matched bool
|
||||
}
|
||||
|
||||
func (hint *indexHintInfo) match(dbName, tblName model.CIStr) bool {
|
||||
return hint.tblName.L == tblName.L &&
|
||||
(hint.dbName.L == dbName.L ||
|
||||
hint.dbName.L == "*") // for universal bindings, e.g. *.t
|
||||
}
|
||||
|
||||
func (hint *indexHintInfo) hintTypeString() string {
|
||||
switch hint.indexHint.HintType {
|
||||
case ast.HintUse:
|
||||
return "use_index"
|
||||
case ast.HintIgnore:
|
||||
return "ignore_index"
|
||||
case ast.HintForce:
|
||||
return "force_index"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// indexString formats the indexHint as dbName.tableName[, indexNames].
|
||||
func (hint *indexHintInfo) indexString() string {
|
||||
var indexListString string
|
||||
indexList := make([]string, len(hint.indexHint.IndexNames))
|
||||
for i := range hint.indexHint.IndexNames {
|
||||
indexList[i] = hint.indexHint.IndexNames[i].L
|
||||
}
|
||||
if len(indexList) > 0 {
|
||||
indexListString = fmt.Sprintf(", %s", strings.Join(indexList, ", "))
|
||||
}
|
||||
return fmt.Sprintf("%s.%s%s", hint.dbName, hint.tblName, indexListString)
|
||||
}
|
||||
|
||||
type aggHintInfo struct {
|
||||
preferAggType uint
|
||||
preferAggToCop bool
|
||||
}
|
||||
|
||||
// QueryTimeRange represents a time range specified by TIME_RANGE hint
|
||||
type QueryTimeRange struct {
|
||||
From time.Time
|
||||
@ -197,234 +103,6 @@ func (tr *QueryTimeRange) MemoryUsage() (sum int64) {
|
||||
return emptyQueryTimeRangeSize
|
||||
}
|
||||
|
||||
func tableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTables []ast.HintTable, p *hint.BlockHintProcessor, currentOffset int) []hintTableInfo {
|
||||
if len(hintTables) == 0 {
|
||||
return nil
|
||||
}
|
||||
hintTableInfos := make([]hintTableInfo, 0, len(hintTables))
|
||||
defaultDBName := model.NewCIStr(ctx.GetSessionVars().CurrentDB)
|
||||
isInapplicable := false
|
||||
for _, hintTable := range hintTables {
|
||||
tableInfo := hintTableInfo{
|
||||
dbName: hintTable.DBName,
|
||||
tblName: hintTable.TableName,
|
||||
partitions: hintTable.PartitionList,
|
||||
selectOffset: p.GetHintOffset(hintTable.QBName, currentOffset),
|
||||
}
|
||||
if tableInfo.dbName.L == "" {
|
||||
tableInfo.dbName = defaultDBName
|
||||
}
|
||||
switch hintName {
|
||||
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ, HintLeading:
|
||||
if len(tableInfo.partitions) > 0 {
|
||||
isInapplicable = true
|
||||
}
|
||||
}
|
||||
hintTableInfos = append(hintTableInfos, tableInfo)
|
||||
}
|
||||
if isInapplicable {
|
||||
ctx.GetSessionVars().StmtCtx.AppendWarning(
|
||||
fmt.Errorf("Optimizer Hint %s is inapplicable on specified partitions",
|
||||
restore2JoinHint(hintName, hintTableInfos)))
|
||||
return nil
|
||||
}
|
||||
return hintTableInfos
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.sortMergeJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferBroadcastJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.broadcastJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferShuffleJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.shuffleJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hashJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noHashJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noMergeJoinTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hjBuildTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferHJProbe(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.hjProbeTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLHJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlhjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferINLMJ(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlmjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inljTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexHashJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inlhjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferNoIndexMergeJoin(tableNames ...*hintTableInfo) bool {
|
||||
return info.matchTableName(tableNames, info.noIndexJoinTables.inlmjTables)
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferTiFlash(tableName *hintTableInfo) *hintTableInfo {
|
||||
if tableName == nil {
|
||||
return nil
|
||||
}
|
||||
for i, tbl := range info.tiflashTables {
|
||||
if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset {
|
||||
info.tiflashTables[i].matched = true
|
||||
return &tbl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (info *tableHintInfo) ifPreferTiKV(tableName *hintTableInfo) *hintTableInfo {
|
||||
if tableName == nil {
|
||||
return nil
|
||||
}
|
||||
for i, tbl := range info.tikvTables {
|
||||
if tableName.dbName.L == tbl.dbName.L && tableName.tblName.L == tbl.tblName.L && tbl.selectOffset == tableName.selectOffset {
|
||||
info.tikvTables[i].matched = true
|
||||
return &tbl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchTableName checks whether the hint hit the need.
|
||||
// Only need either side matches one on the list.
|
||||
// Even though you can put 2 tables on the list,
|
||||
// it doesn't mean optimizer will reorder to make them
|
||||
// join directly.
|
||||
// Which it joins on with depend on sequence of traverse
|
||||
// and without reorder, user might adjust themselves.
|
||||
// This is similar to MySQL hints.
|
||||
func (*tableHintInfo) matchTableName(tables []*hintTableInfo, hintTables []hintTableInfo) bool {
|
||||
hintMatched := false
|
||||
for _, table := range tables {
|
||||
for i, curEntry := range hintTables {
|
||||
if table == nil {
|
||||
continue
|
||||
}
|
||||
if (curEntry.dbName.L == table.dbName.L || curEntry.dbName.L == "*") &&
|
||||
curEntry.tblName.L == table.tblName.L &&
|
||||
table.selectOffset == curEntry.selectOffset {
|
||||
hintTables[i].matched = true
|
||||
hintMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hintMatched
|
||||
}
|
||||
|
||||
func restore2TableHint(hintTables ...hintTableInfo) string {
|
||||
buffer := bytes.NewBufferString("")
|
||||
for i, table := range hintTables {
|
||||
buffer.WriteString(table.tblName.L)
|
||||
if len(table.partitions) > 0 {
|
||||
buffer.WriteString(" PARTITION(")
|
||||
for j, partition := range table.partitions {
|
||||
if j > 0 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
buffer.WriteString(partition.L)
|
||||
}
|
||||
buffer.WriteString(")")
|
||||
}
|
||||
if i < len(hintTables)-1 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2JoinHint(hintType string, hintTables []hintTableInfo) string {
|
||||
if len(hintTables) == 0 {
|
||||
return strings.ToUpper(hintType)
|
||||
}
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(hintType))
|
||||
buffer.WriteString("(")
|
||||
buffer.WriteString(restore2TableHint(hintTables...))
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2IndexHint(hintType string, hintIndex indexHintInfo) string {
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(hintType))
|
||||
buffer.WriteString("(")
|
||||
buffer.WriteString(restore2TableHint(hintTableInfo{
|
||||
dbName: hintIndex.dbName,
|
||||
tblName: hintIndex.tblName,
|
||||
partitions: hintIndex.partitions,
|
||||
}))
|
||||
if hintIndex.indexHint != nil && len(hintIndex.indexHint.IndexNames) > 0 {
|
||||
for i, indexName := range hintIndex.indexHint.IndexNames {
|
||||
if i > 0 {
|
||||
buffer.WriteString(",")
|
||||
}
|
||||
buffer.WriteString(" " + indexName.L)
|
||||
}
|
||||
}
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func restore2StorageHint(tiflashTables, tikvTables []hintTableInfo) string {
|
||||
buffer := bytes.NewBufferString("/*+ ")
|
||||
buffer.WriteString(strings.ToUpper(HintReadFromStorage))
|
||||
buffer.WriteString("(")
|
||||
if len(tiflashTables) > 0 {
|
||||
buffer.WriteString("tiflash[")
|
||||
buffer.WriteString(restore2TableHint(tiflashTables...))
|
||||
buffer.WriteString("]")
|
||||
if len(tikvTables) > 0 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if len(tikvTables) > 0 {
|
||||
buffer.WriteString("tikv[")
|
||||
buffer.WriteString(restore2TableHint(tikvTables...))
|
||||
buffer.WriteString("]")
|
||||
}
|
||||
buffer.WriteString(") */")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func extractUnmatchedTables(hintTables []hintTableInfo) []string {
|
||||
var tableNames []string
|
||||
for _, table := range hintTables {
|
||||
if !table.matched {
|
||||
tableNames = append(tableNames, table.tblName.O)
|
||||
}
|
||||
}
|
||||
return tableNames
|
||||
}
|
||||
|
||||
// clauseCode indicates in which clause the column is currently.
|
||||
type clauseCode int
|
||||
|
||||
@ -517,14 +195,6 @@ const (
|
||||
handlingScalarSubquery
|
||||
)
|
||||
|
||||
// Hint flags listed here are used by PlanBuilder.subQueryHintFlags.
|
||||
const (
|
||||
// HintFlagSemiJoinRewrite corresponds to HintSemiJoinRewrite.
|
||||
HintFlagSemiJoinRewrite uint64 = 1 << iota
|
||||
// HintFlagNoDecorrelate corresponds to HintNoDecorrelate.
|
||||
HintFlagNoDecorrelate
|
||||
)
|
||||
|
||||
// PlanBuilder builds Plan from an ast.Node.
|
||||
// It just builds the ast node straightforwardly.
|
||||
type PlanBuilder struct {
|
||||
|
||||
Reference in New Issue
Block a user