planner: support OR list nested in AND list for mv index (#51716)
close pingcap/tidb#51778
This commit is contained in:
@ -19,6 +19,7 @@ go_library(
|
||||
"hashcode.go",
|
||||
"hint_utils.go",
|
||||
"indexmerge_path.go",
|
||||
"indexmerge_unfinished_path.go",
|
||||
"initialize.go",
|
||||
"logical_plan_builder.go",
|
||||
"logical_plans.go",
|
||||
|
||||
@ -138,7 +138,7 @@ func (ds *DataSource) generateIndexMergePath() error {
|
||||
func (ds *DataSource) generateNormalIndexPartialPaths4DNF(
|
||||
dnfItems []expression.Expression,
|
||||
candidatePaths []*util.AccessPath,
|
||||
) (paths []*util.AccessPath, needSelection bool, usedMap []bool, err error) {
|
||||
) (paths []*util.AccessPath, needSelection bool, usedMap []bool) {
|
||||
paths = make([]*util.AccessPath, 0, len(dnfItems))
|
||||
usedMap = make([]bool, len(dnfItems))
|
||||
for offset, item := range dnfItems {
|
||||
@ -160,7 +160,7 @@ func (ds *DataSource) generateNormalIndexPartialPaths4DNF(
|
||||
// for this dnf item, we couldn't generate an index merge partial path.
|
||||
// (1 member of (a)) or (3 member of (b)) or d=1; if one dnf item like d=1 here could walk index path,
|
||||
// the entire index merge is not valid anymore.
|
||||
return nil, false, usedMap, nil
|
||||
return nil, false, usedMap
|
||||
}
|
||||
// prune out global indexes.
|
||||
itemPaths = slices.DeleteFunc(itemPaths, func(path *util.AccessPath) bool {
|
||||
@ -174,7 +174,7 @@ func (ds *DataSource) generateNormalIndexPartialPaths4DNF(
|
||||
// for this dnf item, we couldn't generate an index merge partial path.
|
||||
// (1 member of (a)) or (3 member of (b)) or d=1; if one dnf item like d=1 here could walk index path,
|
||||
// the entire index merge is not valid anymore.
|
||||
return nil, false, usedMap, nil
|
||||
return nil, false, usedMap
|
||||
}
|
||||
|
||||
// identify whether all pushedDownCNFItems are fully used.
|
||||
@ -196,7 +196,7 @@ func (ds *DataSource) generateNormalIndexPartialPaths4DNF(
|
||||
usedMap[offset] = true
|
||||
paths = append(paths, partialPath)
|
||||
}
|
||||
return paths, needSelection, usedMap, nil
|
||||
return paths, needSelection, usedMap
|
||||
}
|
||||
|
||||
// getIndexMergeOrPath generates all possible IndexMergeOrPaths.
|
||||
@ -485,44 +485,6 @@ func (ds *DataSource) buildIndexMergeOrPath(
|
||||
return indexMergePath
|
||||
}
|
||||
|
||||
func (ds *DataSource) generateNormalIndexPartialPath4Or(dnfItems []expression.Expression, usedAccessMap []bool, normalPathCnt int) ([]*util.AccessPath, []bool, bool, error) {
|
||||
remainedDNFItems := make([]expression.Expression, 0, len(dnfItems))
|
||||
for i, b := range usedAccessMap {
|
||||
if !b {
|
||||
remainedDNFItems = append(remainedDNFItems, dnfItems[i])
|
||||
}
|
||||
}
|
||||
noMVIndexPartialPath := false
|
||||
if len(dnfItems) == len(remainedDNFItems) {
|
||||
// there is no mv index paths generated, so for: (a<1) OR (a>2), no need to generated index merge.
|
||||
noMVIndexPartialPath = true
|
||||
}
|
||||
paths, needSelection, usedMap, err := ds.generateNormalIndexPartialPaths4DNF(remainedDNFItems, ds.possibleAccessPaths[:normalPathCnt])
|
||||
if err != nil {
|
||||
return nil, usedAccessMap, false, err
|
||||
}
|
||||
// If all the partialPaths use the same index, we will not use the indexMerge.
|
||||
singlePath := true
|
||||
for i := len(paths) - 1; i >= 1; i-- {
|
||||
if paths[i].Index != paths[i-1].Index {
|
||||
singlePath = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if singlePath && noMVIndexPartialPath {
|
||||
return nil, usedAccessMap, false, nil
|
||||
}
|
||||
// collect the remain filter's used map.
|
||||
cnt := 0
|
||||
for i, b := range usedAccessMap {
|
||||
if !b {
|
||||
usedAccessMap[i] = usedMap[cnt]
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return paths, usedAccessMap, needSelection, nil
|
||||
}
|
||||
|
||||
func (ds *DataSource) generateNormalIndexPartialPath4And(normalPathCnt int, usedAccessMap map[string]expression.Expression) []*util.AccessPath {
|
||||
if res := ds.generateIndexMergeAndPaths(normalPathCnt, usedAccessMap); res != nil {
|
||||
return res.PartialIndexPaths
|
||||
@ -666,115 +628,6 @@ func (ds *DataSource) generateIndexMergeAndPaths(normalPathCnt int, usedAccessMa
|
||||
return indexMergePath
|
||||
}
|
||||
|
||||
/*
|
||||
select * from t where ((1 member of (a) and b=1) or (2 member of (a) and b=2)) and (c > 10)
|
||||
|
||||
IndexMerge(OR)
|
||||
IndexRangeScan(a, b, [1 1, 1 1])
|
||||
IndexRangeScan(a, b, [2 2, 2 2])
|
||||
Selection(c > 10)
|
||||
TableRowIdScan(t)
|
||||
|
||||
Two limitations now:
|
||||
1). Not support the embedded index merge case, which (DNF_Item1 OR DNF_Item2), for every DNF item,
|
||||
try to map it into a simple normal index path or mv index path, other than an internal index merge path.
|
||||
2). Every dnf item should exactly be used as full length index range or prefix index range, other than being not used.
|
||||
*/
|
||||
func (ds *DataSource) generateMVIndexPartialPath4Or(normalPathCnt int, indexMergeDNFConds []expression.Expression) ([]*util.AccessPath, []bool, bool, error) {
|
||||
// step1: collect all mv index paths
|
||||
possibleMVIndexPaths := make([]*util.AccessPath, 0, len(ds.possibleAccessPaths))
|
||||
for idx := 0; idx < normalPathCnt; idx++ {
|
||||
if !isMVIndexPath(ds.possibleAccessPaths[idx]) {
|
||||
continue // not a MVIndex path
|
||||
}
|
||||
if !ds.isInIndexMergeHints(ds.possibleAccessPaths[idx].Index.Name.L) {
|
||||
continue
|
||||
}
|
||||
possibleMVIndexPaths = append(possibleMVIndexPaths, ds.possibleAccessPaths[idx])
|
||||
}
|
||||
// step2: mapping index merge conditions into possible mv index path
|
||||
mvAndPartialPaths := make([]*util.AccessPath, 0, len(possibleMVIndexPaths))
|
||||
usedMap := make([]bool, len(indexMergeDNFConds))
|
||||
needSelection := false
|
||||
|
||||
for offset, dnfCond := range indexMergeDNFConds {
|
||||
var cnfConds []expression.Expression
|
||||
sf, ok := dnfCond.(*expression.ScalarFunction)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cnfConds = expression.SplitCNFItems(sf)
|
||||
// for every dnf condition, find the most suitable mv index path.
|
||||
// otherwise, for table(a json, b json, c int, idx(c,a), idx2(b,c))
|
||||
// condition: (1 member of (a) and c=1 and d=2) or (2 member of (b) and c=3 and d=2);
|
||||
// will both pick(c,a) idx with range [1 1, 1 1] and [3,3], the latter can pick the most
|
||||
// valuable index idx2 with range [2 3,2 3]
|
||||
var (
|
||||
bestPaths []*util.AccessPath
|
||||
bestCountAfterAccess float64
|
||||
bestNeedSelection bool
|
||||
)
|
||||
for _, onePossibleMVIndexPath := range possibleMVIndexPaths {
|
||||
idxCols, ok := PrepareIdxColsAndUnwrapArrayType(
|
||||
ds.table.Meta(),
|
||||
onePossibleMVIndexPath.Index,
|
||||
ds.TblCols,
|
||||
true,
|
||||
)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// for every cnfCond, try to map it into possible mv index path.
|
||||
// remainingFilters is not cared here, because it will be all suspended on the table side.
|
||||
accessFilters, remainingFilters, _ := collectFilters4MVIndex(ds.SCtx(), cnfConds, idxCols)
|
||||
if len(accessFilters) == 0 {
|
||||
continue
|
||||
}
|
||||
paths, isIntersection, ok, err := buildPartialPaths4MVIndex(ds.SCtx(), accessFilters, idxCols, onePossibleMVIndexPath.Index, ds.tableStats.HistColl)
|
||||
if err != nil {
|
||||
logutil.BgLogger().Debug("build index merge partial mv index paths failed", zap.Error(err))
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if !ok || len(paths) == 0 {
|
||||
continue
|
||||
}
|
||||
// only under 2 cases we can fallthrough it.
|
||||
// 1: the index merge only has one partial path.
|
||||
// 2: index merge is UNION type.
|
||||
canFallThrough := len(paths) == 1 || !isIntersection
|
||||
if !canFallThrough {
|
||||
continue
|
||||
}
|
||||
// UNION case, use the max count after access for simplicity.
|
||||
maxCountAfterAccess := -1.0
|
||||
for _, p := range paths {
|
||||
maxCountAfterAccess = math.Max(maxCountAfterAccess, p.CountAfterAccess)
|
||||
}
|
||||
// Note that: here every path is about mv index path.
|
||||
// find the most valuable mv index path, which means it has the minimum countAfterAccess.
|
||||
if len(bestPaths) == 0 {
|
||||
bestPaths = paths
|
||||
bestCountAfterAccess = maxCountAfterAccess
|
||||
bestNeedSelection = len(remainingFilters) != 0
|
||||
} else if bestCountAfterAccess > maxCountAfterAccess {
|
||||
bestPaths = paths
|
||||
bestCountAfterAccess = maxCountAfterAccess
|
||||
bestNeedSelection = len(remainingFilters) != 0
|
||||
}
|
||||
}
|
||||
if len(bestPaths) != 0 {
|
||||
usedMap[offset] = true
|
||||
// correctly find a dnf condition for this mv index path
|
||||
mvAndPartialPaths = append(mvAndPartialPaths, bestPaths...)
|
||||
if !needSelection && bestNeedSelection {
|
||||
// collect one path's need selection flag.
|
||||
needSelection = bestNeedSelection
|
||||
}
|
||||
}
|
||||
}
|
||||
return mvAndPartialPaths, usedMap, needSelection, nil
|
||||
}
|
||||
|
||||
// generateMVIndexMergePartialPaths4And try to find mv index merge partial path from a collection of cnf conditions.
|
||||
func (ds *DataSource) generateMVIndexMergePartialPaths4And(normalPathCnt int, indexMergeConds []expression.Expression, histColl *statistics.HistColl) ([]*util.AccessPath, map[string]expression.Expression, error) {
|
||||
// step1: collect all mv index paths
|
||||
@ -953,62 +806,28 @@ func (ds *DataSource) generateIndexMergeOnDNF4MVIndex(normalPathCnt int, filters
|
||||
continue
|
||||
}
|
||||
|
||||
idxCols, ok := PrepareIdxColsAndUnwrapArrayType(
|
||||
ds.table.Meta(),
|
||||
ds.possibleAccessPaths[idx].Index,
|
||||
ds.TblCols,
|
||||
true,
|
||||
)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for current, filter := range filters {
|
||||
sf, ok := filter.(*expression.ScalarFunction)
|
||||
if !ok || sf.FuncName.L != ast.LogicOr {
|
||||
continue
|
||||
}
|
||||
dnfFilters := expression.FlattenDNFConditions(sf) // [(1 member of (a) and b=1), (2 member of (a) and b=2)]
|
||||
dnfFilters := expression.SplitDNFItems(sf) // [(1 member of (a) and b=1), (2 member of (a) and b=2)]
|
||||
|
||||
// build partial paths for each dnf filter
|
||||
cannotFit := false
|
||||
var partialPaths []*util.AccessPath
|
||||
for _, dnfFilter := range dnfFilters {
|
||||
mvIndexFilters := []expression.Expression{dnfFilter}
|
||||
if sf, ok := dnfFilter.(*expression.ScalarFunction); ok && sf.FuncName.L == ast.LogicAnd {
|
||||
mvIndexFilters = expression.FlattenCNFConditions(sf) // (1 member of (a) and b=1) --> [(1 member of (a)), b=1]
|
||||
}
|
||||
|
||||
accessFilters, remainingFilters, _ := collectFilters4MVIndex(ds.SCtx(), mvIndexFilters, idxCols)
|
||||
if len(accessFilters) == 0 || len(remainingFilters) > 0 { // limitation 1
|
||||
cannotFit = true
|
||||
break
|
||||
}
|
||||
paths, isIntersection, ok, err := buildPartialPaths4MVIndex(ds.SCtx(), accessFilters, idxCols, ds.possibleAccessPaths[idx].Index, ds.tableStats.HistColl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isIntersection || !ok { // limitation 2
|
||||
cannotFit = true
|
||||
break
|
||||
}
|
||||
partialPaths = append(partialPaths, paths...)
|
||||
}
|
||||
if cannotFit {
|
||||
continue
|
||||
}
|
||||
|
||||
var remainingFilters []expression.Expression
|
||||
remainingFilters = append(remainingFilters, filters[:current]...)
|
||||
remainingFilters = append(remainingFilters, filters[current+1:]...)
|
||||
|
||||
indexMergePath := ds.buildPartialPathUp4MVIndex(
|
||||
partialPaths,
|
||||
false,
|
||||
remainingFilters,
|
||||
ds.tableStats.HistColl,
|
||||
unfinishedIndexMergePath := generateUnfinishedIndexMergePathFromORList(
|
||||
ds,
|
||||
dnfFilters,
|
||||
[]*util.AccessPath{ds.possibleAccessPaths[idx]},
|
||||
)
|
||||
mvIndexPaths = append(mvIndexPaths, indexMergePath)
|
||||
finishedIndexMergePath := handleTopLevelANDListAndGenFinishedPath(
|
||||
ds,
|
||||
filters,
|
||||
current,
|
||||
[]*util.AccessPath{ds.possibleAccessPaths[idx]},
|
||||
unfinishedIndexMergePath,
|
||||
)
|
||||
if finishedIndexMergePath != nil {
|
||||
mvIndexPaths = append(mvIndexPaths, finishedIndexMergePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -1086,57 +905,67 @@ func (ds *DataSource) generateIndexMerge4ComposedIndex(normalPathCnt int, indexM
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(indexMergeConds) == 1 {
|
||||
// Collect access paths that satisfy the hints, and make sure there is at least one MV index path.
|
||||
var mvIndexPathCnt int
|
||||
candidateAccessPaths := make([]*util.AccessPath, 0, len(ds.possibleAccessPaths))
|
||||
for idx := 0; idx < normalPathCnt; idx++ {
|
||||
if ds.possibleAccessPaths[idx].Index != nil && ds.possibleAccessPaths[idx].Index.Global {
|
||||
continue
|
||||
}
|
||||
if (ds.possibleAccessPaths[idx].IsTablePath() &&
|
||||
!ds.isInIndexMergeHints("primary")) ||
|
||||
(!ds.possibleAccessPaths[idx].IsTablePath() &&
|
||||
!ds.isInIndexMergeHints(ds.possibleAccessPaths[idx].Index.Name.L)) {
|
||||
continue
|
||||
}
|
||||
if isMVIndexPath(ds.possibleAccessPaths[idx]) {
|
||||
mvIndexPathCnt++
|
||||
}
|
||||
candidateAccessPaths = append(candidateAccessPaths, ds.possibleAccessPaths[idx])
|
||||
}
|
||||
if mvIndexPathCnt == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for current, filter := range indexMergeConds {
|
||||
// DNF path.
|
||||
sf, ok := indexMergeConds[0].(*expression.ScalarFunction)
|
||||
sf, ok := filter.(*expression.ScalarFunction)
|
||||
if !ok || sf.FuncName.L != ast.LogicOr {
|
||||
// targeting: cond1 or cond2 or cond3
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
dnfFilters := expression.FlattenDNFConditions(sf)
|
||||
mvIndexPartialPaths, usedAccessMap, needSelection4MVIndex, err := ds.generateMVIndexPartialPath4Or(normalPathCnt, dnfFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(mvIndexPartialPaths) == 0 {
|
||||
// no mv index partial paths to be composed of.
|
||||
return nil
|
||||
}
|
||||
normalIndexPartialPaths, usedAccessMap, needSelection4NormalIndex, err := ds.generateNormalIndexPartialPath4Or(dnfFilters, usedAccessMap, normalPathCnt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// since multi normal index merge path is handled before, here focus on multi mv index merge, or mv and normal mixed index merge
|
||||
composed := (len(mvIndexPartialPaths) > 1) || (len(mvIndexPartialPaths) == 1 && len(normalIndexPartialPaths) >= 1)
|
||||
if !composed {
|
||||
return nil
|
||||
}
|
||||
// if any cnf item is not used as index partial path, index merge is not valid anymore.
|
||||
if slices.Contains(usedAccessMap, false) {
|
||||
return nil
|
||||
}
|
||||
// todo: make this code as a portal of all index merge path.
|
||||
// if we derive:
|
||||
// 1: some mv index partial path, no normal index path, it means multi mv index merge.
|
||||
// 2: some mv index partial path, some normal index path, it means hybrid index merge.
|
||||
// 3: no mv index partial path, several normal index path, it means multi normal index merge.
|
||||
combinedPartialPaths := append(normalIndexPartialPaths, mvIndexPartialPaths...)
|
||||
if len(combinedPartialPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
// here we directly use the all index merge conditions as the table filers for simplicity.
|
||||
// todo: make estimation more correct rather than pruning other index merge path.
|
||||
var indexMergeTableFilters []expression.Expression
|
||||
if needSelection4MVIndex || needSelection4NormalIndex {
|
||||
indexMergeTableFilters = indexMergeConds
|
||||
}
|
||||
mvp := ds.buildPartialPathUp4MVIndex(
|
||||
combinedPartialPaths,
|
||||
false,
|
||||
indexMergeTableFilters,
|
||||
ds.tableStats.HistColl,
|
||||
dnfFilters := expression.SplitDNFItems(sf)
|
||||
|
||||
unfinishedIndexMergePath := generateUnfinishedIndexMergePathFromORList(
|
||||
ds,
|
||||
dnfFilters,
|
||||
candidateAccessPaths,
|
||||
)
|
||||
ds.possibleAccessPaths = append(ds.possibleAccessPaths, mvp)
|
||||
finishedIndexMergePath := handleTopLevelANDListAndGenFinishedPath(
|
||||
ds,
|
||||
indexMergeConds,
|
||||
current,
|
||||
candidateAccessPaths,
|
||||
unfinishedIndexMergePath,
|
||||
)
|
||||
if finishedIndexMergePath == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var mvIndexPartialPathCnt, normalIndexPartialPathCnt int
|
||||
for _, path := range finishedIndexMergePath.PartialIndexPaths {
|
||||
if isMVIndexPath(path) {
|
||||
mvIndexPartialPathCnt++
|
||||
} else {
|
||||
normalIndexPartialPathCnt++
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the same behavior with previous implementation, we only handle the "composed" case here.
|
||||
if mvIndexPartialPathCnt == 0 || (mvIndexPartialPathCnt == 1 && normalIndexPartialPathCnt == 0) {
|
||||
return nil
|
||||
}
|
||||
ds.possibleAccessPaths = append(ds.possibleAccessPaths, finishedIndexMergePath)
|
||||
return nil
|
||||
}
|
||||
// CNF path.
|
||||
@ -1437,13 +1266,13 @@ func buildPartialPath4MVIndex(
|
||||
// This function is exported for test.
|
||||
func PrepareIdxColsAndUnwrapArrayType(
|
||||
tableInfo *model.TableInfo,
|
||||
mvIndex *model.IndexInfo,
|
||||
idxInfo *model.IndexInfo,
|
||||
tblCols []*expression.Column,
|
||||
checkOnly1ArrayTypeCol bool,
|
||||
) (idxCols []*expression.Column, ok bool) {
|
||||
var virColNum = 0
|
||||
for i := range mvIndex.Columns {
|
||||
colOffset := mvIndex.Columns[i].Offset
|
||||
for i := range idxInfo.Columns {
|
||||
colOffset := idxInfo.Columns[i].Offset
|
||||
colMeta := tableInfo.Cols()[colOffset]
|
||||
var col *expression.Column
|
||||
for _, c := range tblCols {
|
||||
@ -1486,7 +1315,7 @@ func collectFilters4MVIndex(
|
||||
if usedAsAccess[i] {
|
||||
continue
|
||||
}
|
||||
if ok, tp := checkFilter4MVIndexColumn(sctx, f, col); ok {
|
||||
if ok, tp := checkAccessFilter4IdxCol(sctx, f, col); ok {
|
||||
accessFilters = append(accessFilters, f)
|
||||
usedAsAccess[i] = true
|
||||
found = true
|
||||
@ -1560,7 +1389,7 @@ func CollectFilters4MVIndexMutations(sctx PlanContext, filters []expression.Expr
|
||||
if usedAsAccess[i] {
|
||||
continue
|
||||
}
|
||||
if ok, _ := checkFilter4MVIndexColumn(sctx, f, col); ok {
|
||||
if ok, _ := checkAccessFilter4IdxCol(sctx, f, col); ok {
|
||||
if col.VirtualExpr != nil && col.VirtualExpr.GetType().IsArray() {
|
||||
// assert jsonColOffset should always be the same.
|
||||
// if the filter is from virtual expression, it means it is about the mv json col.
|
||||
@ -1652,10 +1481,11 @@ const (
|
||||
singleValueOnMVColTp
|
||||
)
|
||||
|
||||
// checkFilter4MVIndexColumn checks whether this filter can be used as an accessFilter to access the MVIndex column, and
|
||||
// which type the access filter is, as defined above.
|
||||
// checkAccessFilter4IdxCol checks whether this filter can be used as an accessFilter to access the column of an index,
|
||||
// and returns which type the access filter is, as defined above.
|
||||
// Though this function is introduced for MV index, it can also be used for normal index
|
||||
// If the return value ok is false, the type must be unspecifiedFilterTp.
|
||||
func checkFilter4MVIndexColumn(
|
||||
func checkAccessFilter4IdxCol(
|
||||
sctx PlanContext,
|
||||
filter expression.Expression,
|
||||
idxCol *expression.Column,
|
||||
|
||||
416
pkg/planner/core/indexmerge_unfinished_path.go
Normal file
416
pkg/planner/core/indexmerge_unfinished_path.go
Normal file
@ -0,0 +1,416 @@
|
||||
// Copyright 2024 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 (
|
||||
"math"
|
||||
"slices"
|
||||
|
||||
"github.com/pingcap/tidb/pkg/expression"
|
||||
"github.com/pingcap/tidb/pkg/parser/model"
|
||||
"github.com/pingcap/tidb/pkg/planner/util"
|
||||
)
|
||||
|
||||
// Note that at this moment, the implementation related to unfinishedAccessPath only aims to handle OR list nested in
|
||||
// AND list, which is like ... AND (... OR ... OR ...) AND ..., and build an MV IndexMerge access path. So some struct
|
||||
// definition and code logic are specially designed for this case or only consider this case.
|
||||
// This may be changed in the future.
|
||||
|
||||
// unfinishedAccessPath is for collecting access filters for an access path.
|
||||
// It maintains the information during iterating all filters. Importantly, it maintains incomplete access filters, which
|
||||
// means they may not be able to build a valid range, but could build a valid range after collecting more access filters.
|
||||
// After iterating all filters, we can check and build it into a valid util.AccessPath.
|
||||
type unfinishedAccessPath struct {
|
||||
index *model.IndexInfo
|
||||
|
||||
accessFilters []expression.Expression
|
||||
|
||||
// To avoid regression and keep the same behavior as the previous implementation, we collect access filters in two
|
||||
// methods:
|
||||
//
|
||||
// 1. Use the same functions as the previous implementation to collect access filters. They are able to handle some
|
||||
// complicated expressions, but the expressions must be able to build into a valid range at once (that's also what
|
||||
// "finished" implies).
|
||||
// In this case, idxColHasAccessFilter will be nil and initedAsFinished will be true.
|
||||
//
|
||||
// 2. Use the new logic, which is to collect access filters for each column respectively, gradually collect more
|
||||
// access filters during iterating all filters and try to form a valid range at last.
|
||||
// In this case, initedAsFinished will be false, and idxColHasAccessFilter will record if we have already collected
|
||||
// a valid access filter for each column of the index.
|
||||
idxColHasAccessFilter []bool
|
||||
initedAsFinished bool
|
||||
|
||||
// needKeepFilter means the OR list need to become a filter in the final Selection.
|
||||
needKeepFilter bool
|
||||
|
||||
// Similar to AccessPath.PartialIndexPaths, each element in the slice is expected to build into a partial AccessPath.
|
||||
// Currently, it can only mean an OR type IndexMerge.
|
||||
indexMergeORPartialPaths []unfinishedAccessPathList
|
||||
}
|
||||
|
||||
// unfinishedAccessPathList is for collecting access filters for a slice of candidate access paths.
|
||||
// This type is useful because usually we have several candidate index/table paths. When we are iterating the
|
||||
// expressions, we want to match them against all index/table paths and try to find access filter for every path. After
|
||||
// iterating all expressions, we check if any of them can form a valid range so that we can build a valid AccessPath.
|
||||
type unfinishedAccessPathList []*unfinishedAccessPath
|
||||
|
||||
// generateUnfinishedIndexMergePathFromORList handles a list of filters connected by OR, collects access filters for
|
||||
// each candidate access path, and returns an unfinishedAccessPath, which must be an index merge OR unfinished path,
|
||||
// each partial path of which corresponds to one filter in the input orList.
|
||||
/*
|
||||
Example:
|
||||
Input:
|
||||
orList: 1 member of j->'$.a' OR 2 member of j->'$.b'
|
||||
candidateAccessPaths: [idx1(a, j->'$.a' unsigned array), idx2(j->'$.b' unsigned array, a)]
|
||||
Output:
|
||||
unfinishedAccessPath{
|
||||
indexMergeORPartialPaths: [
|
||||
// Collect access filters for (1 member of j->'$.a') using two candidates respectively.
|
||||
[unfinishedAccessPath{idx1,1 member of j->'$.a'}, nil]
|
||||
// Collect access filters for (2 member of j->'$.b') using two candidates respectively.
|
||||
[nil, unfinishedAccessPath{idx2,2 member of j->'$.b'}]
|
||||
]
|
||||
}
|
||||
*/
|
||||
func generateUnfinishedIndexMergePathFromORList(
|
||||
ds *DataSource,
|
||||
orList []expression.Expression,
|
||||
candidateAccessPaths []*util.AccessPath,
|
||||
) *unfinishedAccessPath {
|
||||
if len(orList) < 2 {
|
||||
return nil
|
||||
}
|
||||
unfinishedPartialPaths := make([]unfinishedAccessPathList, 0, len(orList))
|
||||
for _, singleFilter := range orList {
|
||||
unfinishedPathList := initUnfinishedPathsFromExpr(ds, candidateAccessPaths, singleFilter)
|
||||
if unfinishedPathList == nil {
|
||||
return nil
|
||||
}
|
||||
unfinishedPartialPaths = append(unfinishedPartialPaths, unfinishedPathList)
|
||||
}
|
||||
return &unfinishedAccessPath{
|
||||
indexMergeORPartialPaths: unfinishedPartialPaths,
|
||||
}
|
||||
}
|
||||
|
||||
// initUnfinishedPathsFromExpr tries to collect access filters from the input filter for each candidate access path,
|
||||
// and returns them as a slice of unfinishedAccessPath, each of which corresponds to an input candidate access path.
|
||||
// If we failed to collect access filters for one candidate access path, the corresponding element in the return slice
|
||||
// will be nil.
|
||||
// If we failed to collect access filters for all candidate access paths, this function will return nil.
|
||||
/*
|
||||
Example1 (consistent with the one in generateUnfinishedIndexMergePathFromORList()):
|
||||
Input:
|
||||
expr: 1 member of j->'$.a'
|
||||
candidateAccessPaths: [idx1(a, j->'$.a' unsigned array), idx2(j->'$.b' unsigned array, a)]
|
||||
Output:
|
||||
[unfinishedAccessPath{idx1,1 member of j->'$.a'}, nil]
|
||||
|
||||
Example2:
|
||||
Input:
|
||||
expr: a = 3
|
||||
candidateAccessPaths: [idx1(a, j->'$.a' unsigned array), idx2(j->'$.b' unsigned array, a)]
|
||||
Output:
|
||||
[unfinishedAccessPath{idx1,a=3}, unfinishedAccessPath{idx2,a=3}]
|
||||
*/
|
||||
func initUnfinishedPathsFromExpr(
|
||||
ds *DataSource,
|
||||
candidateAccessPaths []*util.AccessPath,
|
||||
expr expression.Expression,
|
||||
) unfinishedAccessPathList {
|
||||
retValues := make([]unfinishedAccessPath, len(candidateAccessPaths))
|
||||
ret := make([]*unfinishedAccessPath, 0, len(candidateAccessPaths))
|
||||
for i := range candidateAccessPaths {
|
||||
ret = append(ret, &retValues[i])
|
||||
}
|
||||
for i, path := range candidateAccessPaths {
|
||||
ret[i].index = path.Index
|
||||
// case 1: try to use the previous logic to handle non-mv index
|
||||
if !isMVIndexPath(path) {
|
||||
// generateNormalIndexPartialPaths4DNF is introduced for handle a slice of DNF items and a slice of
|
||||
// candidate AccessPaths before, now we reuse it to handle single filter and single candidate AccessPath,
|
||||
// so we need to wrap them in a slice here.
|
||||
paths, needSelection, usedMap := ds.generateNormalIndexPartialPaths4DNF(
|
||||
[]expression.Expression{expr},
|
||||
[]*util.AccessPath{path},
|
||||
)
|
||||
if len(usedMap) == 1 && usedMap[0] && len(paths) == 1 {
|
||||
ret[i].initedAsFinished = true
|
||||
ret[i].accessFilters = paths[0].AccessConds
|
||||
ret[i].needKeepFilter = needSelection
|
||||
continue
|
||||
}
|
||||
}
|
||||
if path.IsTablePath() {
|
||||
continue
|
||||
}
|
||||
idxCols, ok := PrepareIdxColsAndUnwrapArrayType(ds.table.Meta(), path.Index, ds.TblCols, false)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cnfItems := expression.SplitCNFItems(expr)
|
||||
|
||||
// case 2: try to use the previous logic to handle mv index
|
||||
if isMVIndexPath(path) {
|
||||
accessFilters, remainingFilters, tp := collectFilters4MVIndex(ds.SCtx(), cnfItems, idxCols)
|
||||
if len(accessFilters) > 0 && (tp == multiValuesOROnMVColTp || tp == singleValueOnMVColTp) {
|
||||
ret[i].initedAsFinished = true
|
||||
ret[i].accessFilters = accessFilters
|
||||
ret[i].needKeepFilter = len(remainingFilters) > 0
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// case 3: use the new logic if the previous logic didn't succeed to collect access filters that can build a
|
||||
// valid range directly.
|
||||
ret[i].idxColHasAccessFilter = make([]bool, len(idxCols))
|
||||
for j, col := range idxCols {
|
||||
for _, cnfItem := range cnfItems {
|
||||
if ok, tp := checkAccessFilter4IdxCol(ds.SCtx(), cnfItem, col); ok &&
|
||||
// Since we only handle the OR list nested in the AND list, and only generate IndexMerge OR path,
|
||||
// we disable the multiValuesANDOnMVColTp case here.
|
||||
(tp == eqOnNonMVColTp || tp == multiValuesOROnMVColTp || tp == singleValueOnMVColTp) {
|
||||
ret[i].accessFilters = append(ret[i].accessFilters, cnfItem)
|
||||
ret[i].idxColHasAccessFilter[j] = true
|
||||
// Once we find one valid access filter for this column, we directly go to the next column without
|
||||
// looking into other filters.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validCnt := 0
|
||||
// remove useless paths
|
||||
for i, path := range ret {
|
||||
if !path.initedAsFinished &&
|
||||
!slices.Contains(path.idxColHasAccessFilter, true) {
|
||||
ret[i] = nil
|
||||
} else {
|
||||
validCnt++
|
||||
}
|
||||
}
|
||||
if validCnt == 0 {
|
||||
return nil
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// handleTopLevelANDListAndGenFinishedPath is expected to be used together with
|
||||
// generateUnfinishedIndexMergePathFromORList() to handle the expression like ... AND (... OR ... OR ...) AND ...
|
||||
// for mv index.
|
||||
// It will try to collect possible access filters from other items in the top level AND list and try to merge them into
|
||||
// the unfinishedAccessPath from generateUnfinishedIndexMergePathFromORList(), and try to build it into a valid
|
||||
// util.AccessPath.
|
||||
// The input candidateAccessPaths argument should be the same with generateUnfinishedIndexMergePathFromORList().
|
||||
func handleTopLevelANDListAndGenFinishedPath(
|
||||
ds *DataSource,
|
||||
allConds []expression.Expression,
|
||||
orListIdxInAllConds int,
|
||||
candidateAccessPaths []*util.AccessPath,
|
||||
unfinishedIndexMergePath *unfinishedAccessPath,
|
||||
) *util.AccessPath {
|
||||
for i, cnfItem := range allConds {
|
||||
// Skip the (... OR ... OR ...) in the list.
|
||||
if i == orListIdxInAllConds {
|
||||
continue
|
||||
}
|
||||
// Collect access filters from one AND item.
|
||||
pathListFromANDItem := initUnfinishedPathsFromExpr(ds, candidateAccessPaths, cnfItem)
|
||||
// Try to merge useful access filters in them into unfinishedIndexMergePath, which is from the nested OR list.
|
||||
unfinishedIndexMergePath = mergeANDItemIntoUnfinishedIndexMergePath(unfinishedIndexMergePath, pathListFromANDItem)
|
||||
}
|
||||
if unfinishedIndexMergePath == nil {
|
||||
return nil
|
||||
}
|
||||
return buildIntoAccessPath(
|
||||
ds,
|
||||
candidateAccessPaths,
|
||||
unfinishedIndexMergePath,
|
||||
allConds,
|
||||
orListIdxInAllConds,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Example (consistent with the one in generateUnfinishedIndexMergePathFromORList()):
|
||||
|
||||
idx1: (a, j->'$.a' unsigned array) idx2: (j->'$.b' unsigned array, a)
|
||||
Input:
|
||||
indexMergePath:
|
||||
unfinishedAccessPath{ indexMergeORPartialPaths:[
|
||||
[unfinishedAccessPath{idx1,1 member of j->'$.a'}, nil]
|
||||
[nil, unfinishedAccessPath{idx2,2 member of j->'$.b'}]
|
||||
]}
|
||||
pathListFromANDItem:
|
||||
[unfinishedAccessPath{idx1,a=3}, unfinishedAccessPath{idx2,a=3}]
|
||||
Output:
|
||||
unfinishedAccessPath{ indexMergeORPartialPaths:[
|
||||
[unfinishedAccessPath{idx1,1 member of j->'$.a', a=3}, nil]
|
||||
[nil, unfinishedAccessPath{idx2,2 member of j->'$.b', a=3}]
|
||||
]}
|
||||
*/
|
||||
func mergeANDItemIntoUnfinishedIndexMergePath(
|
||||
indexMergePath *unfinishedAccessPath,
|
||||
pathListFromANDItem unfinishedAccessPathList,
|
||||
) *unfinishedAccessPath {
|
||||
// Currently, we only handle the case where indexMergePath is an index merge OR unfinished path and
|
||||
// pathListFromANDItem is a normal unfinished path or nil
|
||||
if indexMergePath == nil || len(indexMergePath.indexMergeORPartialPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
// This means we failed to find any valid access filter from other expressions in the top level AND list.
|
||||
// In this case, we ignore them and only rely on the nested OR list to try to build a IndexMerge OR path.
|
||||
if pathListFromANDItem == nil {
|
||||
return indexMergePath
|
||||
}
|
||||
for _, pathListForSinglePartialPath := range indexMergePath.indexMergeORPartialPaths {
|
||||
if len(pathListForSinglePartialPath) != len(pathListFromANDItem) {
|
||||
continue
|
||||
}
|
||||
for i, path := range pathListForSinglePartialPath {
|
||||
if path == nil || pathListFromANDItem[i] == nil {
|
||||
continue
|
||||
}
|
||||
// We don't do precise checks. As long as any columns have valid access filters, we collect the entire
|
||||
// access filters from the AND item.
|
||||
// We just collect as many possibly useful access filters as possible, buildIntoAccessPath() should handle
|
||||
// them correctly.
|
||||
if pathListFromANDItem[i].initedAsFinished ||
|
||||
slices.Contains(pathListFromANDItem[i].idxColHasAccessFilter, true) {
|
||||
path.accessFilters = append(path.accessFilters, pathListFromANDItem[i].accessFilters...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexMergePath
|
||||
}
|
||||
|
||||
func buildIntoAccessPath(
|
||||
ds *DataSource,
|
||||
originalPaths []*util.AccessPath,
|
||||
indexMergePath *unfinishedAccessPath,
|
||||
allConds []expression.Expression,
|
||||
orListIdxInAllConds int,
|
||||
) *util.AccessPath {
|
||||
if indexMergePath == nil || len(indexMergePath.indexMergeORPartialPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
var needSelectionGlobal bool
|
||||
|
||||
// 1. Generate one or more partial access path for each partial unfinished path (access filter on mv index may
|
||||
// produce several partial paths).
|
||||
partialPaths := make([]*util.AccessPath, 0, len(indexMergePath.indexMergeORPartialPaths))
|
||||
|
||||
// for each partial path
|
||||
for _, unfinishedPathList := range indexMergePath.indexMergeORPartialPaths {
|
||||
var (
|
||||
bestPaths []*util.AccessPath
|
||||
bestCountAfterAccess float64
|
||||
bestNeedSelection bool
|
||||
)
|
||||
|
||||
// for each possible access path of this partial path
|
||||
for i, unfinishedPath := range unfinishedPathList {
|
||||
if unfinishedPath == nil {
|
||||
continue
|
||||
}
|
||||
var paths []*util.AccessPath
|
||||
var needSelection bool
|
||||
if unfinishedPath.index != nil && unfinishedPath.index.MVIndex {
|
||||
// case 1: mv index
|
||||
idxCols, ok := PrepareIdxColsAndUnwrapArrayType(
|
||||
ds.table.Meta(),
|
||||
unfinishedPath.index,
|
||||
ds.TblCols,
|
||||
true,
|
||||
)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
accessFilters, remainingFilters, _ := collectFilters4MVIndex(
|
||||
ds.SCtx(),
|
||||
unfinishedPath.accessFilters,
|
||||
idxCols,
|
||||
)
|
||||
if len(accessFilters) == 0 {
|
||||
continue
|
||||
}
|
||||
var isIntersection bool
|
||||
var err error
|
||||
paths, isIntersection, ok, err = buildPartialPaths4MVIndex(
|
||||
ds.SCtx(),
|
||||
accessFilters,
|
||||
idxCols,
|
||||
unfinishedPath.index,
|
||||
ds.tableStats.HistColl,
|
||||
)
|
||||
if err != nil || !ok || (isIntersection && len(paths) > 1) {
|
||||
continue
|
||||
}
|
||||
needSelection = len(remainingFilters) > 0 || len(unfinishedPath.idxColHasAccessFilter) > 0
|
||||
} else {
|
||||
// case 2: non-mv index
|
||||
var usedMap []bool
|
||||
// Reuse the previous implementation. The same usage as in initUnfinishedPathsFromExpr().
|
||||
paths, needSelection, usedMap = ds.generateNormalIndexPartialPaths4DNF(
|
||||
[]expression.Expression{
|
||||
expression.ComposeCNFCondition(
|
||||
ds.SCtx().GetExprCtx(),
|
||||
unfinishedPath.accessFilters...,
|
||||
),
|
||||
},
|
||||
[]*util.AccessPath{originalPaths[i]},
|
||||
)
|
||||
if len(paths) != 1 || slices.Contains(usedMap, false) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
needSelection = needSelection || unfinishedPath.needKeepFilter
|
||||
// If there are several partial paths, we use the max CountAfterAccess for comparison.
|
||||
maxCountAfterAccess := -1.0
|
||||
for _, p := range paths {
|
||||
maxCountAfterAccess = math.Max(maxCountAfterAccess, p.CountAfterAccess)
|
||||
}
|
||||
// Choose the best partial path for this partial path.
|
||||
if len(bestPaths) == 0 {
|
||||
bestPaths = paths
|
||||
bestCountAfterAccess = maxCountAfterAccess
|
||||
bestNeedSelection = needSelection
|
||||
} else if bestCountAfterAccess > maxCountAfterAccess {
|
||||
bestPaths = paths
|
||||
bestCountAfterAccess = maxCountAfterAccess
|
||||
bestNeedSelection = needSelection
|
||||
}
|
||||
}
|
||||
if len(bestPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Succeeded to get valid path(s) for this partial path.
|
||||
partialPaths = append(partialPaths, bestPaths...)
|
||||
needSelectionGlobal = needSelectionGlobal || bestNeedSelection
|
||||
}
|
||||
|
||||
// 2. Collect the final table filter
|
||||
// We always put all filters in the top level AND list except for the OR list into the final table filters.
|
||||
// Whether to put the OR list into the table filters also depends on the needSelectionGlobal.
|
||||
tableFilter := allConds[:]
|
||||
if !needSelectionGlobal {
|
||||
tableFilter = slices.Delete(tableFilter, orListIdxInAllConds, orListIdxInAllConds+1)
|
||||
}
|
||||
|
||||
// 3. Build the final access path
|
||||
ret := ds.buildPartialPathUp4MVIndex(partialPaths, false, tableFilter, ds.tableStats.HistColl)
|
||||
return ret
|
||||
}
|
||||
@ -1411,7 +1411,7 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) {
|
||||
verifyPlanCacheForMVIndex(t, tk, true, true,
|
||||
`select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (? member of (a) and c=? and d=?) or (? member of (b) and c=? and d=?)`,
|
||||
`int`, `int`, `int`, `int`, `int`, `int`)
|
||||
verifyPlanCacheForMVIndex(t, tk, true, false,
|
||||
verifyPlanCacheForMVIndex(t, tk, false, false,
|
||||
`select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_contains(a, ?) and c=? and d=?) or (? member of (b) and c=? and d=?)`,
|
||||
`json-signed`, `int`, `int`, `int`, `int`, `int`)
|
||||
verifyPlanCacheForMVIndex(t, tk, true, false,
|
||||
|
||||
@ -54,11 +54,10 @@ IndexMerge_9 0.20 root type: union
|
||||
└─TableRowIDScan_7 0.20 cop[tikv] table:t2 keep order:false, stats:pseudo
|
||||
explain select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_contains(a, '[1, 2, 3]') and c=1 and d=2) or (2 member of (b) and c=3 and d=2); -- 4: OR index merge from multi complicated mv index (memberof),make full use of DNF item's condition even if the predicate is intersection case (json_contains);
|
||||
id estRows task access object operator info
|
||||
IndexMerge_9 0.01 root type: union
|
||||
├─IndexRangeScan_5(Build) 10.00 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1,1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_6(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 3,2 3], keep order:false, stats:pseudo
|
||||
└─Selection_8(Probe) 0.01 cop[tikv] or(and(json_contains(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
|
||||
└─TableRowIDScan_7 10.10 cop[tikv] table:t2 keep order:false, stats:pseudo
|
||||
IndexLookUp_11 8.00 root
|
||||
├─IndexRangeScan_8(Build) 10.00 cop[tikv] table:t2, index:idx3(c, d) range:[1 2,1 2], [3 2,3 2], keep order:false, stats:pseudo
|
||||
└─Selection_10(Probe) 8.00 cop[tikv] or(and(json_contains(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
|
||||
└─TableRowIDScan_9 10.00 cop[tikv] table:t2 keep order:false, stats:pseudo
|
||||
explain select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_overlaps(a, '[1, 2, 3]') and c=1 and d=2) or (2 member of (b) and c=3 and d=2); -- 5: OR index merge from multi complicated mv index (memberof),make full use of DNF item's condition even if the predicate is intersection case (json_contains);
|
||||
id estRows task access object operator info
|
||||
Selection_5 0.32 root or(and(json_overlaps(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
|
||||
@ -90,9 +89,9 @@ TableReader_7 10.18 root data:Selection_6
|
||||
explain select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where (1 member of (a) and 1 member of (b) and c=3) or (3 member of (b) and c=4) or d=1; -- 9: OR index merge from multi complicated mv index (memberof), each DNF item should be strict or lax used as index partial path, specify the index in index merge hint;
|
||||
id estRows task access object operator info
|
||||
IndexMerge_10 0.01 root type: union
|
||||
├─IndexRangeScan_5(Build) 10.00 cop[tikv] table:t2, index:idx4(d) range:[1,1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_6(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[3 1,3 1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_7(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[3 4,3 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_5(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[3 1,3 1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_6(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[3 4,3 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan_7(Build) 10.00 cop[tikv] table:t2, index:idx4(d) range:[1,1], keep order:false, stats:pseudo
|
||||
└─Selection_9(Probe) 0.01 cop[tikv] or(and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.a), and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 3))), or(and(json_memberof(cast(3, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 4)), eq(planner__core__casetest__index__index.t2.d, 1)))
|
||||
└─TableRowIDScan_8 10.20 cop[tikv] table:t2 keep order:false, stats:pseudo
|
||||
drop table if exists t1, t2;
|
||||
|
||||
@ -744,7 +744,7 @@ key mvi2(a, (cast(j->'$.c' as unsigned array))),
|
||||
key mvi3((cast(j->'$.d' as unsigned array)), c),
|
||||
key idx(b, c)
|
||||
);
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
json_overlaps(j->'$.a', '[4,5,6]') or
|
||||
(2 member of (j->'$.a'))
|
||||
@ -753,10 +753,14 @@ c = 10 and
|
||||
a = 20;
|
||||
id estRows task access object operator info
|
||||
Selection 0.01 root or(json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY)), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")))
|
||||
└─TableReader 0.01 root data:Selection
|
||||
└─Selection 0.01 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 10)
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
└─IndexMerge 0.40 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 4,10 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 5,10 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 6,10 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 2,10 2], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.40 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 10)
|
||||
└─TableRowIDScan 0.40 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
@ -764,11 +768,20 @@ c = 3
|
||||
) and
|
||||
json_overlaps(j->'$.a', '[4,5,6]');
|
||||
id estRows task access object operator info
|
||||
Selection 24.00 root json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY))
|
||||
└─TableReader 30.00 root data:Selection
|
||||
└─Selection 30.00 cop[tikv] or(eq(planner__core__indexmerge_path.t.c, 1), or(eq(planner__core__indexmerge_path.t.c, 2), eq(planner__core__indexmerge_path.t.c, 3)))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
Selection 0.72 root json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY))
|
||||
└─IndexMerge 0.90 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[1 4,1 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[1 5,1 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[1 6,1 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[2 4,2 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[2 5,2 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[2 6,2 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 4,3 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 5,3 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 6,3 6], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.90 cop[tikv] or(eq(planner__core__indexmerge_path.t.c, 1), or(eq(planner__core__indexmerge_path.t.c, 2), eq(planner__core__indexmerge_path.t.c, 3)))
|
||||
└─TableRowIDScan 0.90 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
@ -779,7 +792,7 @@ id estRows task access object operator info
|
||||
TableReader 24.00 root data:Selection
|
||||
└─Selection 24.00 cop[tikv] json_contains(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY)), or(eq(planner__core__indexmerge_path.t.c, 1), or(eq(planner__core__indexmerge_path.t.c, 2), eq(planner__core__indexmerge_path.t.c, 3)))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
@ -787,10 +800,13 @@ c = 3
|
||||
) and
|
||||
json_contains(j->'$.a', '[2]');
|
||||
id estRows task access object operator info
|
||||
TableReader 24.00 root data:Selection
|
||||
└─Selection 24.00 cop[tikv] json_contains(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[2]", json BINARY)), or(eq(planner__core__indexmerge_path.t.c, 1), or(eq(planner__core__indexmerge_path.t.c, 2), eq(planner__core__indexmerge_path.t.c, 3)))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
IndexMerge 0.30 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[1 2,1 2], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[2 2,2 2], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 2,3 2], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.30 cop[tikv] json_contains(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[2]", json BINARY)), or(eq(planner__core__indexmerge_path.t.c, 1), or(eq(planner__core__indexmerge_path.t.c, 2), eq(planner__core__indexmerge_path.t.c, 3)))
|
||||
└─TableRowIDScan 0.30 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
2 member of (j->'$.a')
|
||||
@ -798,10 +814,12 @@ explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
c = 10 and
|
||||
a = 20;
|
||||
id estRows task access object operator info
|
||||
TableReader 0.01 root data:Selection
|
||||
└─Selection 0.01 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 10), or(json_memberof(cast(1, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi1) */ * from t where
|
||||
IndexMerge 0.20 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 1,10 1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 2,10 2], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.20 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 10), or(json_memberof(cast(1, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")))
|
||||
└─TableRowIDScan 0.20 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
2 member of (j->'$.d')
|
||||
@ -811,7 +829,7 @@ id estRows task access object operator info
|
||||
TableReader 8.00 root data:Selection
|
||||
└─Selection 8.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), or(json_memberof(cast(1, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.d")))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi3) */ * from t where
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi3) */ * from t where
|
||||
c = 5 and
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
@ -819,10 +837,12 @@ c = 5 and
|
||||
) and
|
||||
a = 20;
|
||||
id estRows task access object operator info
|
||||
TableReader 0.01 root data:Selection
|
||||
└─Selection 0.01 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 5), or(json_memberof(cast(1, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.d")))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2) */ * from t where
|
||||
IndexMerge 0.20 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[5 1,5 1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi3(cast(json_extract(`j`, _utf8mb4'$.d') as unsigned array), c) range:[2 5,2 5], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.20 cop[tikv] eq(planner__core__indexmerge_path.t.a, 20), eq(planner__core__indexmerge_path.t.c, 5), or(json_memberof(cast(1, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(2, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.d")))
|
||||
└─TableRowIDScan 0.20 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, primary) */ * from t where
|
||||
(
|
||||
pk = 2 or
|
||||
json_overlaps(j->'$.a', '[4,5,6]') or
|
||||
@ -833,11 +853,15 @@ b = '2' and
|
||||
c = 3;
|
||||
id estRows task access object operator info
|
||||
Selection 0.00 root or(eq(planner__core__indexmerge_path.t.pk, 2), or(json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY)), json_contains(json_extract(planner__core__indexmerge_path.t.j, "$.c"), cast("[3]", json BINARY))))
|
||||
└─IndexLookUp 0.00 root
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:idx(b, c) range:["2" 3,"2" 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1)
|
||||
└─TableRowIDScan 0.10 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2) */ * from t where
|
||||
└─IndexMerge 0.00 root type: union
|
||||
├─TableRangeScan(Build) 1.00 cop[tikv] table:t range:[2,2], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.00 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 4 "2",3 4 "2"], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.00 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 5 "2",3 5 "2"], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.00 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 6 "2",3 6 "2"], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi2(a, cast(json_extract(`j`, _utf8mb4'$.c') as unsigned array)) range:[1 3,1 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), eq(planner__core__indexmerge_path.t.b, "2"), eq(planner__core__indexmerge_path.t.c, 3)
|
||||
└─TableRowIDScan 1.10 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, primary) */ * from t where
|
||||
a = 1 and
|
||||
b = '2' and
|
||||
c = 3 and
|
||||
@ -847,11 +871,13 @@ pk = 2 or
|
||||
(3 member of (j->'$.c'))
|
||||
);
|
||||
id estRows task access object operator info
|
||||
IndexLookUp 0.00 root
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:idx(b, c) range:["2" 3,"2" 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(eq(planner__core__indexmerge_path.t.pk, 2), or(json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 0.10 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
IndexMerge 0.00 root type: union
|
||||
├─TableRangeScan(Build) 1.00 cop[tikv] table:t range:[2,2], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.00 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[3 3 "2",3 3 "2"], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi2(a, cast(json_extract(`j`, _utf8mb4'$.c') as unsigned array)) range:[1 3,1 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), eq(planner__core__indexmerge_path.t.b, "2"), eq(planner__core__indexmerge_path.t.c, 3), or(eq(planner__core__indexmerge_path.t.pk, 2), or(json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a")), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 1.10 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
b = '2' and
|
||||
(
|
||||
@ -860,11 +886,13 @@ c = 20 or
|
||||
3 member of (j->'$.c')
|
||||
);
|
||||
id estRows task access object operator info
|
||||
IndexLookUp 0.01 root
|
||||
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:idx(b, c) range:["2","2"], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.01 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(eq(planner__core__indexmerge_path.t.c, 20), or(and(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a"))), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 10.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
IndexMerge 0.20 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:idx(b, c) range:["2" 20,"2" 20], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.00 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 3 "2",10 3 "2"], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi2(a, cast(json_extract(`j`, _utf8mb4'$.c') as unsigned array)) range:[1 3,1 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.20 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), eq(planner__core__indexmerge_path.t.b, "2"), or(eq(planner__core__indexmerge_path.t.c, 20), or(and(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.a"))), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 0.20 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
(json_overlaps(j->'$.a', '[4,5,6]')) and
|
||||
(
|
||||
@ -874,10 +902,15 @@ c = 10 or
|
||||
);
|
||||
id estRows task access object operator info
|
||||
Selection 6.41 root json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY))
|
||||
└─TableReader 8.01 root data:Selection
|
||||
└─Selection 8.01 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(and(eq(planner__core__indexmerge_path.t.b, "2"), gt(planner__core__indexmerge_path.t.c, 20)), or(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
└─IndexMerge 0.03 root type: union
|
||||
├─IndexRangeScan(Build) 33.33 cop[tikv] table:t, index:idx(b, c) range:("2" 20,"2" +inf], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 4,10 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 5,10 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 6,10 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi2(a, cast(json_extract(`j`, _utf8mb4'$.c') as unsigned array)) range:[1 3,1 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.03 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(and(eq(planner__core__indexmerge_path.t.b, "2"), gt(planner__core__indexmerge_path.t.c, 20)), or(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 33.73 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
(json_overlaps(j->'$.a', '[4,5,6]')) and
|
||||
(
|
||||
@ -886,7 +919,27 @@ a = 1 and
|
||||
(3 member of (j->'$.c'))
|
||||
);
|
||||
id estRows task access object operator info
|
||||
Selection 6.40 root json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY))
|
||||
└─TableReader 8.00 root data:Selection
|
||||
└─Selection 8.00 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(and(gt(planner__core__indexmerge_path.t.b, "2"), eq(planner__core__indexmerge_path.t.c, 20)), or(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
Selection 0.56 root json_overlaps(json_extract(planner__core__indexmerge_path.t.j, "$.a"), cast("[4,5,6]", json BINARY))
|
||||
└─IndexMerge 0.70 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[20 4,20 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[20 5,20 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[20 6,20 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 4,10 4], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 5,10 5], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi1(c, cast(json_extract(`j`, _utf8mb4'$.a') as unsigned array), b) range:[10 6,10 6], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t, index:mvi2(a, cast(json_extract(`j`, _utf8mb4'$.c') as unsigned array)) range:[1 3,1 3], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.70 cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(and(gt(planner__core__indexmerge_path.t.b, "2"), eq(planner__core__indexmerge_path.t.c, 20)), or(eq(planner__core__indexmerge_path.t.c, 10), json_memberof(cast(3, json BINARY), json_extract(planner__core__indexmerge_path.t.j, "$.c"))))
|
||||
└─TableRowIDScan 0.70 cop[tikv] table:t keep order:false, stats:pseudo
|
||||
create table t1 (a int, b int, c int, d int, j json, key kb(b, (cast(j as unsigned array))), key(d, c));
|
||||
explain format=brief select * from t1 where (c=1 or b=1) and (1 member of (j));
|
||||
id estRows task access object operator info
|
||||
TableReader 15.99 root data:Selection
|
||||
└─Selection 15.99 cop[tikv] json_memberof(cast(1, json BINARY), planner__core__indexmerge_path.t1.j), or(eq(planner__core__indexmerge_path.t1.c, 1), eq(planner__core__indexmerge_path.t1.b, 1))
|
||||
└─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo
|
||||
explain format=brief select * from t1 where (c=1 or b=1) and (1 member of (j)) and d=1;
|
||||
id estRows task access object operator info
|
||||
IndexMerge 0.20 root type: union
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t1, index:d(d, c) range:[1 1,1 1], keep order:false, stats:pseudo
|
||||
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t1, index:kb(b, cast(`j` as unsigned array)) range:[1 1,1 1], keep order:false, stats:pseudo
|
||||
└─Selection(Probe) 0.20 cop[tikv] eq(planner__core__indexmerge_path.t1.d, 1), json_memberof(cast(1, json BINARY), planner__core__indexmerge_path.t1.j), or(eq(planner__core__indexmerge_path.t1.c, 1), eq(planner__core__indexmerge_path.t1.b, 1))
|
||||
└─TableRowIDScan 0.20 cop[tikv] table:t1 keep order:false, stats:pseudo
|
||||
|
||||
@ -272,55 +272,63 @@ key mvi2(a, (cast(j->'$.c' as unsigned array))),
|
||||
key mvi3((cast(j->'$.d' as unsigned array)), c),
|
||||
key idx(b, c)
|
||||
);
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
json_overlaps(j->'$.a', '[4,5,6]') or
|
||||
(2 member of (j->'$.a'))
|
||||
) and
|
||||
c = 10 and
|
||||
a = 20;
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
c = 3
|
||||
) and
|
||||
json_overlaps(j->'$.a', '[4,5,6]');
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
c = 3
|
||||
) and
|
||||
(json_contains(j->'$.a', '[4,5,6]'));
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
c = 1 or
|
||||
c = 2 or
|
||||
c = 3
|
||||
) and
|
||||
json_contains(j->'$.a', '[2]');
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
2 member of (j->'$.a')
|
||||
) and
|
||||
c = 10 and
|
||||
a = 20;
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi1) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1) */ * from t where
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
2 member of (j->'$.d')
|
||||
) and
|
||||
a = 20;
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi3) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi3) */ * from t where
|
||||
c = 5 and
|
||||
(
|
||||
1 member of (j->'$.a') or
|
||||
2 member of (j->'$.d')
|
||||
) and
|
||||
a = 20;
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, primary) */ * from t where
|
||||
(
|
||||
pk = 2 or
|
||||
json_overlaps(j->'$.a', '[4,5,6]') or
|
||||
@ -329,7 +337,8 @@ explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2) */ * from t wher
|
||||
a = 1 and
|
||||
b = '2' and
|
||||
c = 3;
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, primary) */ * from t where
|
||||
a = 1 and
|
||||
b = '2' and
|
||||
c = 3 and
|
||||
@ -338,7 +347,8 @@ c = 3 and
|
||||
(3 member of (j->'$.a')) or
|
||||
(3 member of (j->'$.c'))
|
||||
);
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
b = '2' and
|
||||
(
|
||||
@ -346,7 +356,8 @@ b = '2' and
|
||||
(c = 10 and 3 member of (j->'$.a')) or
|
||||
3 member of (j->'$.c')
|
||||
);
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
(json_overlaps(j->'$.a', '[4,5,6]')) and
|
||||
(
|
||||
@ -354,7 +365,8 @@ a = 1 and
|
||||
c = 10 or
|
||||
3 member of (j->'$.c')
|
||||
);
|
||||
explain format=brief select /*+ use_index_merge(t1, mvi1, mvi2, idx) */ * from t where
|
||||
|
||||
explain format=brief select /*+ use_index_merge(t, mvi1, mvi2, idx) */ * from t where
|
||||
a = 1 and
|
||||
(json_overlaps(j->'$.a', '[4,5,6]')) and
|
||||
(
|
||||
@ -362,3 +374,7 @@ a = 1 and
|
||||
(c = 10) or
|
||||
(3 member of (j->'$.c'))
|
||||
);
|
||||
|
||||
create table t1 (a int, b int, c int, d int, j json, key kb(b, (cast(j as unsigned array))), key(d, c));
|
||||
explain format=brief select * from t1 where (c=1 or b=1) and (1 member of (j));
|
||||
explain format=brief select * from t1 where (c=1 or b=1) and (1 member of (j)) and d=1;
|
||||
|
||||
Reference in New Issue
Block a user