From bfcea6d9a4ac8c026608a8a75eb95084f1d3c1aa Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Tue, 3 Jul 2018 20:23:22 +0800 Subject: [PATCH] plan: remove other `accessPath`s if one is unique key and full matched. (#6925) (#6966) --- plan/logical_plans.go | 51 ++++++++++++++++++++++++++++------ plan/stats.go | 16 +++++++++-- statistics/selectivity_test.go | 4 +-- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/plan/logical_plans.go b/plan/logical_plans.go index 13b4af57bd452..083016e2c8aa4 100644 --- a/plan/logical_plans.go +++ b/plan/logical_plans.go @@ -326,7 +326,9 @@ type accessPath struct { forced bool } -func (ds *DataSource) deriveTablePathStats(path *accessPath) error { +// deriveTablePathStats will fulfill the information that the accessPath need. +// And it will check whether the primary key is covered only by point query. +func (ds *DataSource) deriveTablePathStats(path *accessPath) (bool, error) { var err error sc := ds.ctx.GetSessionVars().StmtCtx path.countAfterAccess = float64(ds.statisticTable.Count) @@ -339,22 +341,33 @@ func (ds *DataSource) deriveTablePathStats(path *accessPath) error { } if pkCol == nil { path.ranges = ranger.FullIntNewRange(false) - return nil + return false, nil } path.ranges = ranger.FullIntNewRange(mysql.HasUnsignedFlag(pkCol.RetType.Flag)) if len(ds.pushedDownConds) == 0 { - return nil + return false, nil } path.accessConds, path.tableFilters = ranger.DetachCondsForTableRange(ds.ctx, ds.pushedDownConds, pkCol) path.ranges, err = ranger.BuildTableRange(path.accessConds, sc, pkCol.RetType) if err != nil { - return errors.Trace(err) + return false, errors.Trace(err) } path.countAfterAccess, err = ds.statisticTable.GetRowCountByIntColumnRanges(sc, pkCol.ID, path.ranges) - return errors.Trace(err) + // Check whether the primary key is covered by point query. + noIntervalRange := true + for _, ran := range path.ranges { + if !ran.IsPoint(sc) { + noIntervalRange = false + break + } + } + return noIntervalRange, errors.Trace(err) } -func (ds *DataSource) deriveIndexPathStats(path *accessPath) error { +// deriveIndexPathStats will fulfill the information that the accessPath need. +// And it will check whether this index is full matched by point query. We will use this check to +// determine whether we remove other paths or not. +func (ds *DataSource) deriveIndexPathStats(path *accessPath) (bool, error) { var err error sc := ds.ctx.GetSessionVars().StmtCtx path.ranges = ranger.FullNewRange() @@ -363,11 +376,11 @@ func (ds *DataSource) deriveIndexPathStats(path *accessPath) error { if len(idxCols) != 0 { path.ranges, path.accessConds, path.indexFilters, path.eqCondCount, err = ranger.DetachCondAndBuildRangeForIndex(ds.ctx, ds.pushedDownConds, idxCols, lengths) if err != nil { - return errors.Trace(err) + return false, errors.Trace(err) } path.countAfterAccess, err = ds.statisticTable.GetRowCountByIndexRanges(sc, path.index.ID, path.ranges) if err != nil { - return errors.Trace(err) + return false, errors.Trace(err) } path.indexFilters, path.tableFilters = splitIndexFilterConditions(path.indexFilters, path.index.Columns, ds.tableInfo) } else { @@ -382,7 +395,27 @@ func (ds *DataSource) deriveIndexPathStats(path *accessPath) error { } path.countAfterIndex = math.Max(path.countAfterAccess*selectivity, ds.statsAfterSelect.count) } - return nil + // Check whether there's only point query. + noIntervalRanges := true + haveNullVal := false + for _, ran := range path.ranges { + // Not point or the not full matched. + if !ran.IsPoint(sc) || len(ran.HighVal) != len(path.index.Columns) { + noIntervalRanges = false + break + } + // Check whether there's null value. + for i := 0; i < len(path.index.Columns); i++ { + if ran.HighVal[i].IsNull() { + haveNullVal = true + break + } + } + if haveNullVal { + break + } + } + return noIntervalRanges && !haveNullVal, nil } func (ds *DataSource) getPKIsHandleCol() *expression.Column { diff --git a/plan/stats.go b/plan/stats.go index c10b133f94f91..3d26c43d5f354 100644 --- a/plan/stats.go +++ b/plan/stats.go @@ -128,16 +128,28 @@ func (ds *DataSource) deriveStats() (*statsInfo, error) { ds.statsAfterSelect = ds.getStatsByFilter(ds.pushedDownConds) for _, path := range ds.possibleAccessPaths { if path.isTablePath { - err := ds.deriveTablePathStats(path) + noIntervalRanges, err := ds.deriveTablePathStats(path) if err != nil { return nil, errors.Trace(err) } + // If there's only point range. Just remove other possible paths. + if noIntervalRanges { + ds.possibleAccessPaths[0] = path + ds.possibleAccessPaths = ds.possibleAccessPaths[:1] + break + } continue } - err := ds.deriveIndexPathStats(path) + noIntervalRanges, err := ds.deriveIndexPathStats(path) if err != nil { return nil, errors.Trace(err) } + // If there's only point range and this index is unique key. Just remove other possible paths. + if noIntervalRanges && path.index.Unique { + ds.possibleAccessPaths[0] = path + ds.possibleAccessPaths = ds.possibleAccessPaths[:1] + break + } } return ds.statsAfterSelect, nil } diff --git a/statistics/selectivity_test.go b/statistics/selectivity_test.go index 832c557c9e2a1..bd64cbd349e5e 100644 --- a/statistics/selectivity_test.go +++ b/statistics/selectivity_test.go @@ -204,8 +204,8 @@ func (s *testSelectivitySuite) TestPseudoSelectivity(c *C) { testKit.MustExec("drop table if exists t, t1") testKit.MustExec("create table t(a int, b int, unique key idx(a,b))") testKit.MustQuery("explain select * from t where a = 1 and b = 1").Check(testkit.Rows( - "IndexScan_8 cop table:t, index:a, b, range:[1 1,1 1], keep order:false 1.00", - "IndexReader_9 root index:IndexScan_8 1.00")) + "IndexScan_5 cop table:t, index:a, b, range:[1 1,1 1], keep order:false 1.00", + "IndexReader_6 root index:IndexScan_5 1.00")) testKit.MustExec("create table t1(a int, b int, primary key(a))") testKit.MustQuery("explain select b from t1 where a = 1").Check(testkit.Rows(