-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
planner: check clustered index don't need double read #18054
Changes from 3 commits
7ce9d17
bf6e3d1
267c6b3
9e6d717
6c37bbf
44eeca6
4ac4e76
6239b61
3910942
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,6 @@ import ( | |
"github.com/pingcap/tidb/planner/util" | ||
"github.com/pingcap/tidb/sessionctx/stmtctx" | ||
"github.com/pingcap/tidb/statistics" | ||
"github.com/pingcap/tidb/table/tables" | ||
"github.com/pingcap/tidb/types" | ||
"github.com/pingcap/tidb/util/chunk" | ||
"github.com/pingcap/tidb/util/logutil" | ||
|
@@ -407,7 +406,7 @@ func (ds *DataSource) skylinePruning(prop *property.PhysicalProperty) []*candida | |
if path.IsTablePath() { | ||
currentCandidate = ds.getTableCandidate(path, prop) | ||
} else { | ||
coveredByIdx := isCoveringIndex(ds.schema.Columns, path.FullIdxCols, path.FullIdxColLens, ds.tableInfo.PKIsHandle) | ||
coveredByIdx := ds.isCoveringIndex(ds.schema.Columns, path.FullIdxCols, path.FullIdxColLens, ds.tableInfo) | ||
if len(path.AccessConds) > 0 || !prop.IsEmpty() || path.Forced || coveredByIdx { | ||
// We will use index to generate physical plan if any of the following conditions is satisfied: | ||
// 1. This path's access cond is not nil. | ||
|
@@ -700,17 +699,19 @@ func (ds *DataSource) buildIndexMergeTableScan(prop *property.PhysicalProperty, | |
return ts, partialCost | ||
} | ||
|
||
func isCoveringIndex(columns, indexColumns []*expression.Column, idxColLens []int, pkIsHandle bool) bool { | ||
func (ds *DataSource) isCoveringIndex(columns, indexColumns []*expression.Column, idxColLens []int, tblInfo *model.TableInfo) bool { | ||
indexCols := append(indexColumns, ds.commonHandleCols...) | ||
indexColLens := append(idxColLens, ds.commonHandleLens...) | ||
for _, col := range columns { | ||
if pkIsHandle && mysql.HasPriKeyFlag(col.RetType.Flag) { | ||
if tblInfo.PKIsHandle && mysql.HasPriKeyFlag(col.RetType.Flag) { | ||
continue | ||
} | ||
if col.ID == model.ExtraHandleID { | ||
continue | ||
} | ||
isIndexColumn := false | ||
for i, indexCol := range indexColumns { | ||
isFullLen := idxColLens[i] == types.UnspecifiedLength || idxColLens[i] == col.RetType.Flen | ||
for i, indexCol := range indexCols { | ||
isFullLen := indexColLens[i] == types.UnspecifiedLength || indexColLens[i] == col.RetType.Flen | ||
// We use col.OrigColName instead of col.ColName. | ||
// Related issue: https://github.com/pingcap/tidb/issues/9636. | ||
if indexCol != nil && col.Equal(nil, indexCol) && isFullLen { | ||
|
@@ -774,7 +775,7 @@ func (ds *DataSource) convertToIndexScan(prop *property.PhysicalProperty, candid | |
cop.cst = cost | ||
task = cop | ||
if candidate.isMatchProp { | ||
if cop.tablePlan != nil { | ||
if cop.tablePlan != nil && !ds.tableInfo.IsCommonHandle { | ||
col, isNew := cop.tablePlan.(*PhysicalTableScan).appendExtraHandleCol(ds) | ||
cop.extraHandleCol = col | ||
cop.doubleReadNeedProj = isNew | ||
|
@@ -814,52 +815,50 @@ func (is *PhysicalIndexScan) indexScanRowSize(idx *model.IndexInfo, ds *DataSour | |
func (is *PhysicalIndexScan) initSchema(idx *model.IndexInfo, idxExprCols []*expression.Column, isDoubleRead bool) { | ||
indexCols := make([]*expression.Column, len(is.IdxCols), len(idx.Columns)+1) | ||
copy(indexCols, is.IdxCols) | ||
for i := len(is.IdxCols); i < len(idx.Columns); i++ { | ||
if idxExprCols[i] != nil { | ||
indexCols = append(indexCols, idxExprCols[i]) | ||
} else { | ||
// TODO: try to reuse the col generated when building the DataSource. | ||
indexCols = append(indexCols, &expression.Column{ | ||
ID: is.Table.Columns[idx.Columns[i].Offset].ID, | ||
RetType: &is.Table.Columns[idx.Columns[i].Offset].FieldType, | ||
UniqueID: is.ctx.GetSessionVars().AllocPlanColumnID(), | ||
}) | ||
} | ||
} | ||
setHandle := len(indexCols) > len(idx.Columns) | ||
if !setHandle { | ||
for i, col := range is.Columns { | ||
if (mysql.HasPriKeyFlag(col.Flag) && is.Table.PKIsHandle) || col.ID == model.ExtraHandleID { | ||
indexCols = append(indexCols, is.dataSourceSchema.Columns[i]) | ||
setHandle = true | ||
break | ||
is.NeedCommonHandle = is.Table.IsCommonHandle | ||
|
||
if !is.NeedCommonHandle { | ||
for i := len(is.IdxCols); i < len(idx.Columns); i++ { | ||
if idxExprCols[i] != nil { | ||
indexCols = append(indexCols, idxExprCols[i]) | ||
} else { | ||
// TODO: try to reuse the col generated when building the DataSource. | ||
indexCols = append(indexCols, &expression.Column{ | ||
ID: is.Table.Columns[idx.Columns[i].Offset].ID, | ||
RetType: &is.Table.Columns[idx.Columns[i].Offset].FieldType, | ||
UniqueID: is.ctx.GetSessionVars().AllocPlanColumnID(), | ||
}) | ||
} | ||
} | ||
} | ||
|
||
if is.Table.IsCommonHandle { | ||
pkIdx := tables.FindPrimaryIndex(is.Table) | ||
for _, col := range pkIdx.Columns { | ||
indexCols = append(indexCols, &expression.Column{ | ||
ID: is.Table.Columns[col.Offset].ID, | ||
RetType: &is.Table.Columns[col.Offset].FieldType, | ||
UniqueID: is.ctx.GetSessionVars().AllocPlanColumnID(), | ||
}) | ||
setHandle := len(indexCols) > len(idx.Columns) | ||
if !setHandle { | ||
for i, col := range is.Columns { | ||
if (mysql.HasPriKeyFlag(col.Flag) && is.Table.PKIsHandle) || col.ID == model.ExtraHandleID { | ||
indexCols = append(indexCols, is.dataSourceSchema.Columns[i]) | ||
setHandle = true | ||
break | ||
} | ||
} | ||
} | ||
is.NeedCommonHandle = true | ||
} | ||
|
||
// If it's double read case, the first index must return handle. So we should add extra handle column | ||
// if there isn't a handle column. | ||
if isDoubleRead && !setHandle { | ||
if !is.Table.IsCommonHandle { | ||
indexCols = append(indexCols, &expression.Column{ | ||
RetType: types.NewFieldType(mysql.TypeLonglong), | ||
ID: model.ExtraHandleID, | ||
UniqueID: is.ctx.GetSessionVars().AllocPlanColumnID(), | ||
}) | ||
// If it's double read case, the first index must return handle. So we should add extra handle column | ||
// if there isn't a handle column. | ||
if isDoubleRead && !setHandle { | ||
if !is.Table.IsCommonHandle { | ||
indexCols = append(indexCols, &expression.Column{ | ||
RetType: types.NewFieldType(mysql.TypeLonglong), | ||
ID: model.ExtraHandleID, | ||
UniqueID: is.ctx.GetSessionVars().AllocPlanColumnID(), | ||
}) | ||
} | ||
} | ||
} else { | ||
if len(is.IdxCols) < len(is.Columns) { | ||
for i := len(is.IdxCols); i < len(idxExprCols); i++ { | ||
indexCols = append(indexCols, idxExprCols[i]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to check if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
} | ||
|
||
is.SetSchema(expression.NewSchema(indexCols...)) | ||
} | ||
|
||
|
@@ -922,11 +921,11 @@ func matchIndicesProp(idxCols []*expression.Column, colLens []int, propItems []p | |
return true | ||
} | ||
|
||
func splitIndexFilterConditions(conditions []expression.Expression, indexColumns []*expression.Column, idxColLens []int, | ||
func (ds *DataSource) splitIndexFilterConditions(conditions []expression.Expression, indexColumns []*expression.Column, idxColLens []int, | ||
table *model.TableInfo) (indexConds, tableConds []expression.Expression) { | ||
var indexConditions, tableConditions []expression.Expression | ||
for _, cond := range conditions { | ||
if isCoveringIndex(expression.ExtractColumns(cond), indexColumns, idxColLens, table.PKIsHandle) { | ||
if ds.isCoveringIndex(expression.ExtractColumns(cond), indexColumns, idxColLens, table) { | ||
indexConditions = append(indexConditions, cond) | ||
} else { | ||
tableConditions = append(tableConditions, cond) | ||
|
@@ -1408,7 +1407,7 @@ func (ds *DataSource) getOriginalPhysicalIndexScan(prop *property.PhysicalProper | |
is.Hist = &statsTbl.Indices[idx.ID].Histogram | ||
} | ||
rowCount := path.CountAfterAccess | ||
is.initSchema(idx, path.FullIdxCols, !isSingleScan) | ||
is.initSchema(idx, append(path.FullIdxCols, ds.commonHandleCols...), !isSingleScan) | ||
// Only use expectedCnt when it's smaller than the count we calculated. | ||
// e.g. IndexScan(count1)->After Filter(count2). The `ds.stats.RowCount` is count2. count1 is the one we need to calculate | ||
// If expectedCnt and count2 are both zero and we go into the below `if` block, the count1 will be set to zero though it's shouldn't be. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2859,6 +2859,15 @@ func (b *PlanBuilder) buildDataSource(ctx context.Context, tn *ast.TableName, as | |
ds.names = names | ||
ds.setPreferredStoreType(b.TableHints()) | ||
|
||
// Init commonHandleCols and commonHandleLens for data source. | ||
if tableInfo.IsCommonHandle { | ||
for _, idx := range tableInfo.Indices { | ||
if idx.Primary { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a function |
||
ds.commonHandleCols, ds.commonHandleLens = expression.IndexInfo2Cols(ds.Columns, ds.schema.Columns, idx) | ||
break | ||
} | ||
} | ||
} | ||
// Init FullIdxCols, FullIdxColLens for accessPaths. | ||
for _, path := range ds.possibleAccessPaths { | ||
if !path.IsTablePath() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can extract this for loop, and call it twice with
indexColumns
andcommonHandleCols
.So we don't need to append slices.