diff --git a/cmd/explaintest/r/explain_complex.result b/cmd/explaintest/r/explain_complex.result index 66d38378b0f33..1edb33e9f4e18 100644 --- a/cmd/explaintest/r/explain_complex.result +++ b/cmd/explaintest/r/explain_complex.result @@ -120,7 +120,7 @@ Projection_13 1.00 root gad.id, test.dd.id, gad.aid, gad.cm, test.dd.dic, test.d └─HashAgg_19 1.00 root group by:gad.aid, test.dd.dic, funcs:firstrow(gad.id), firstrow(gad.aid), firstrow(gad.cm), firstrow(gad.p1), firstrow(gad.p2), firstrow(gad.p3), firstrow(gad.p4), firstrow(gad.p5), firstrow(gad.p6_md5), firstrow(gad.p7_md5), firstrow(gad.ext), firstrow(gad.t), firstrow(test.dd.id), firstrow(test.dd.dic), firstrow(test.dd.ip), firstrow(test.dd.t) └─IndexJoin_24 0.00 root inner join, inner:IndexLookUp_23, outer key:gad.aid, inner key:test.dd.aid, other cond:eq(test.dd.ip, gad.ip), gt(test.dd.t, gad.t) ├─IndexLookUp_23 0.00 root - │ ├─IndexScan_20 10.00 cop table:dd, index:aid, dic, range: decided by [gad.aid gad.ip], keep order:false, stats:pseudo + │ ├─IndexScan_20 10.00 cop table:dd, index:aid, dic, range: decided by [gad.aid], keep order:false, stats:pseudo │ └─Selection_22 0.00 cop eq(test.dd.bm, 0), eq(test.dd.pt, "android"), gt(test.dd.t, 1478143908), not(isnull(test.dd.ip)), not(isnull(test.dd.t)) │ └─TableScan_21 10.00 cop table:dd, keep order:false, stats:pseudo └─IndexLookUp_33 3.33 root @@ -137,7 +137,7 @@ Projection_10 0.00 root gad.id, sdk.id, gad.aid, gad.cm, sdk.dic, sdk.ip, sdk.t, │ └─Selection_26 0.00 cop eq(gad.bm, 0), eq(gad.dit, "mac"), eq(gad.pt, "ios"), not(isnull(gad.dic)) │ └─TableScan_25 3333.33 cop table:st, keep order:false, stats:pseudo └─IndexLookUp_17 0.00 root - ├─IndexScan_14 10.00 cop table:sdk, index:aid, dic, range: decided by [gad.aid gad.dic], keep order:false, stats:pseudo + ├─IndexScan_14 10.00 cop table:sdk, index:aid, dic, range: decided by [gad.aid], keep order:false, stats:pseudo └─Selection_16 0.00 cop eq(sdk.bm, 0), eq(sdk.pt, "ios"), gt(sdk.t, 1477971479), not(isnull(sdk.mac)), not(isnull(sdk.t)) └─TableScan_15 10.00 cop table:dd, keep order:false, stats:pseudo explain SELECT cm, p1, p2, p3, p4, p5, p6_md5, p7_md5, count(1) as click_pv, count(DISTINCT ip) as click_ip FROM st WHERE (t between 1478188800 and 1478275200) and aid='cn.sbkcq' and pt='android' GROUP BY cm, p1, p2, p3, p4, p5, p6_md5, p7_md5; diff --git a/cmd/explaintest/r/explain_complex_stats.result b/cmd/explaintest/r/explain_complex_stats.result index 805695e77132c..1348939387d47 100644 --- a/cmd/explaintest/r/explain_complex_stats.result +++ b/cmd/explaintest/r/explain_complex_stats.result @@ -133,7 +133,7 @@ Projection_13 424.00 root gad.id, test.dd.id, gad.aid, gad.cm, test.dd.dic, test │ └─Selection_28 424.00 cop eq(gad.bm, 0), eq(gad.pt, "android"), gt(gad.t, 1478143908), not(isnull(gad.ip)) │ └─TableScan_27 1999.00 cop table:gad, range:[0,+inf], keep order:false └─IndexLookUp_23 455.80 root - ├─IndexScan_20 1.00 cop table:dd, index:aid, dic, range: decided by [gad.aid gad.ip], keep order:false + ├─IndexScan_20 1.00 cop table:dd, index:aid, dic, range: decided by [gad.aid], keep order:false └─Selection_22 455.80 cop eq(test.dd.bm, 0), eq(test.dd.pt, "android"), gt(test.dd.t, 1478143908), not(isnull(test.dd.ip)), not(isnull(test.dd.t)) └─TableScan_21 1.00 cop table:dd, keep order:false explain select gad.id as gid,sdk.id as sid,gad.aid as aid,gad.cm as cm,sdk.dic as dic,sdk.ip as ip, sdk.t as t, gad.p1 as p1, gad.p2 as p2, gad.p3 as p3, gad.p4 as p4, gad.p5 as p5, gad.p6_md5 as p6, gad.p7_md5 as p7, gad.ext as ext from st gad join dd sdk on gad.aid = sdk.aid and gad.dic = sdk.mac and gad.t < sdk.t where gad.t > 1477971479 and gad.bm = 0 and gad.pt = 'ios' and gad.dit = 'mac' and sdk.t > 1477971479 and sdk.bm = 0 and sdk.pt = 'ios' limit 3000; @@ -145,7 +145,7 @@ Projection_10 170.34 root gad.id, sdk.id, gad.aid, gad.cm, sdk.dic, sdk.ip, sdk. │ └─Selection_22 170.34 cop eq(gad.bm, 0), eq(gad.dit, "mac"), eq(gad.pt, "ios"), gt(gad.t, 1477971479), not(isnull(gad.dic)) │ └─TableScan_21 1999.00 cop table:gad, range:[0,+inf], keep order:false └─IndexLookUp_17 509.04 root - ├─IndexScan_14 1.00 cop table:sdk, index:aid, dic, range: decided by [gad.aid gad.dic], keep order:false + ├─IndexScan_14 1.00 cop table:sdk, index:aid, dic, range: decided by [gad.aid], keep order:false └─Selection_16 509.04 cop eq(sdk.bm, 0), eq(sdk.pt, "ios"), gt(sdk.t, 1477971479), not(isnull(sdk.mac)), not(isnull(sdk.t)) └─TableScan_15 1.00 cop table:dd, keep order:false explain SELECT cm, p1, p2, p3, p4, p5, p6_md5, p7_md5, count(1) as click_pv, count(DISTINCT ip) as click_ip FROM st WHERE (t between 1478188800 and 1478275200) and aid='cn.sbkcq' and pt='android' GROUP BY cm, p1, p2, p3, p4, p5, p6_md5, p7_md5; diff --git a/cmd/explaintest/r/tpch.result b/cmd/explaintest/r/tpch.result index 8bdb543a3814a..85b90f3a79a64 100644 --- a/cmd/explaintest/r/tpch.result +++ b/cmd/explaintest/r/tpch.result @@ -612,7 +612,7 @@ Sort_25 2406.00 root profit.nation:asc, profit.o_year:desc │ └─TableReader_40 1.00 root data:TableScan_39 │ └─TableScan_39 1.00 cop table:orders, range: decided by [tpch.lineitem.l_orderkey], keep order:false └─IndexLookUp_34 1.00 root - ├─IndexScan_32 1.00 cop table:partsupp, index:PS_PARTKEY, PS_SUPPKEY, range: decided by [tpch.lineitem.l_suppkey tpch.lineitem.l_partkey], keep order:false + ├─IndexScan_32 1.00 cop table:partsupp, index:PS_PARTKEY, PS_SUPPKEY, range: decided by [tpch.lineitem.l_partkey tpch.lineitem.l_suppkey], keep order:false └─TableScan_33 1.00 cop table:partsupp, keep order:false /* Q10 Returned Item Reporting Query diff --git a/planner/core/cbo_test.go b/planner/core/cbo_test.go index d9e2758a921fd..1e1c48554680e 100644 --- a/planner/core/cbo_test.go +++ b/planner/core/cbo_test.go @@ -118,6 +118,63 @@ func (s *testAnalyzeSuite) TestCBOWithoutAnalyze(c *C) { )) } +func (s *testAnalyzeSuite) TestIssue8058(c *C) { + defer testleak.AfterTest(c)() + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer func() { + dom.Close() + store.Close() + }() + + testKit := testkit.NewTestKit(c, store) + testKit.MustExec(`use test`) + testKit.MustExec(`create table n (pid int(11) not null, nid int(11) not null, primary key(pid, nid), key i_nid(nid))`) + testKit.MustExec(`create table w (nid int(11) not null, day date not null, hour smallint(6) not null, cid bigint(20) not null, cnt int (11) not null, primary key(nid, day, hour, cid))`) + testKit.MustQuery(`explain select count(1) from w join n on w.nid = n.nid where n.pid = 863 and cid in(73151457924606867) and day = '2018-10-23'`).Check(testkit.Rows( + "StreamAgg_13 1.00 root funcs:count(1)", + "└─IndexJoin_17 0.01 root inner join, inner:IndexReader_16, outer key:test.w.nid, inner key:test.n.nid", + " ├─TableReader_31 0.01 root data:Selection_30", + " │ └─Selection_30 0.01 cop eq(test.w.day, 2018-10-23 00:00:00.000000), in(test.w.cid, 73151457924606867)", + " │ └─TableScan_29 10000.00 cop table:w, range:[-inf,+inf], keep order:false, stats:pseudo", + " └─IndexReader_16 10.00 root index:IndexScan_15", + " └─IndexScan_15 10.00 cop table:n, index:pid, nid, range: decided by [863 test.w.nid,863 test.w.nid], keep order:false, stats:pseudo", + )) + testKit.MustQuery(`explain select count(1) from w join n on w.nid = n.nid where n.pid in (863, 534) and cid in(73151457924606867) and day = '2018-10-23'`).Check(testkit.Rows( + "StreamAgg_13 1.00 root funcs:count(1)", + "└─IndexJoin_17 0.01 root inner join, inner:IndexReader_16, outer key:test.w.nid, inner key:test.n.nid", + " ├─TableReader_35 0.01 root data:Selection_34", + " │ └─Selection_34 0.01 cop eq(test.w.day, 2018-10-23 00:00:00.000000), in(test.w.cid, 73151457924606867)", + " │ └─TableScan_33 10000.00 cop table:w, range:[-inf,+inf], keep order:false, stats:pseudo", + " └─IndexReader_16 10.00 root index:IndexScan_15", + " └─IndexScan_15 10.00 cop table:n, index:pid, nid, range: decided by [534 test.w.nid,534 test.w.nid], [863 test.w.nid,863 test.w.nid], keep order:false, stats:pseudo", + )) + + testKit.MustExec(`create table t1(a int not null, b int not null, c int not null, d int not null, index idx_ab(a, b, c, d))`) + testKit.MustExec(`create table t2(a int not null, b int not null, c int not null, d int not null)`) + testKit.MustQuery(`explain select /*+ TIDB_INLJ(t1, t2) */ count(1) from t1 join t2 on t1.d = t2.d and t1.a = t2.a where t1.b = 2`).Check(testkit.Rows( + "StreamAgg_12 1.00 root funcs:count(1)", + "└─IndexJoin_20 12.50 root inner join, inner:IndexReader_19, outer key:test.t2.a, inner key:test.t1.a, other cond:eq(test.t1.d, test.t2.d)", + " ├─IndexReader_19 10.00 root index:IndexScan_18", + " │ └─IndexScan_18 10.00 cop table:t1, index:a, b, c, d, range: decided by [test.t2.a 2,test.t2.a 2], keep order:false, stats:pseudo", + " └─TableReader_22 10000.00 root data:TableScan_21", + " └─TableScan_21 10000.00 cop table:t2, range:[-inf,+inf], keep order:false, stats:pseudo", + )) + + testKit.MustExec(`drop table if exists t1, t2`) + testKit.MustExec(`create table t1(a int not null, b int not null, index idx_ab(a, b))`) + testKit.MustExec(`create table t2(a int not null, b int not null, c int not null)`) + testKit.MustQuery(`explain select /*+ TIDB_INLJ(t1, t2) */ count(1) from t1 join t2 on t1.b = t2.b and t1.a = t2.a where t2.c = 1000`).Check(testkit.Rows( + "StreamAgg_12 1.00 root funcs:count(1)", + "└─IndexJoin_21 12.50 root inner join, inner:IndexReader_20, outer key:test.t2.b, test.t2.a, inner key:test.t1.b, test.t1.a", + " ├─IndexReader_20 10.00 root index:IndexScan_19", + " │ └─IndexScan_19 10.00 cop table:t1, index:a, b, range: decided by [test.t2.a test.t2.b], keep order:false, stats:pseudo", + " └─TableReader_24 10.00 root data:Selection_23", + " └─Selection_23 10.00 cop eq(test.t2.c, 1000)", + " └─TableScan_22 10000.00 cop table:t2, range:[-inf,+inf], keep order:false, stats:pseudo", + )) +} + func (s *testAnalyzeSuite) TestStraightJoin(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index ed53f86b772a3..f02688ed6357b 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -427,7 +427,7 @@ func (p *LogicalJoin) getIndexJoinByOuterIdx(prop *property.PhysicalProperty, ou } } if bestIndexInfo != nil { - innerPlan := p.constructInnerIndexScan(ds, bestIndexInfo, remainedOfBest, outerJoinKeys, us) + innerPlan := p.constructInnerIndexScan(ds, bestIndexInfo, rangesOfBest, remainedOfBest, outerJoinKeys, us, keyOff2IdxOff) return p.constructIndexJoin(prop, innerJoinKeys, outerJoinKeys, outerIdx, innerPlan, rangesOfBest, keyOff2IdxOff) } return nil @@ -480,7 +480,7 @@ func (p *LogicalJoin) constructInnerUnionScan(us *LogicalUnionScan, reader Physi } // constructInnerIndexScan is specially used to construct the inner plan for PhysicalIndexJoin. -func (p *LogicalJoin) constructInnerIndexScan(ds *DataSource, idx *model.IndexInfo, remainedConds []expression.Expression, outerJoinKeys []*expression.Column, us *LogicalUnionScan) PhysicalPlan { +func (p *LogicalJoin) constructInnerIndexScan(ds *DataSource, idx *model.IndexInfo, ranges []*ranger.Range, remainedConds []expression.Expression, outerJoinKeys []*expression.Column, us *LogicalUnionScan, keyOff2IdxOff []int) PhysicalPlan { is := PhysicalIndexScan{ Table: ds.tableInfo, TableAsName: ds.TableAsName, @@ -490,7 +490,10 @@ func (p *LogicalJoin) constructInnerIndexScan(ds *DataSource, idx *model.IndexIn dataSourceSchema: ds.schema, KeepOrder: false, Ranges: ranger.FullRange(), - rangeDecidedBy: outerJoinKeys, + + keyOff2IdxOff: keyOff2IdxOff, + rangesWithFakeConds: ranges, + rangeDecidedBy: outerJoinKeys, }.Init(ds.ctx) is.filterCondition = remainedConds diff --git a/planner/core/explain.go b/planner/core/explain.go index ae0ebcbdd8515..471e6418dbffa 100644 --- a/planner/core/explain.go +++ b/planner/core/explain.go @@ -58,7 +58,13 @@ func (p *PhysicalIndexScan) ExplainInfo() string { } } if len(p.rangeDecidedBy) > 0 { - fmt.Fprintf(buffer, ", range: decided by %v", p.rangeDecidedBy) + fmt.Fprintf(buffer, ", range: decided by ") + for i, idxRange := range p.rangesWithFakeConds { + fmt.Fprintf(buffer, idxRange.StringWithJoinKeys(p.rangeDecidedBy, p.keyOff2IdxOff)) + if i+1 < len(p.rangesWithFakeConds) { + fmt.Fprint(buffer, ", ") + } + } } else if haveCorCol { fmt.Fprintf(buffer, ", range: decided by %v", p.AccessCondition) } else if len(p.Ranges) > 0 { diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index 77e7529eedbd3..20f783d21063f 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -15,6 +15,7 @@ package core_test import ( "context" + "log" . "github.com/pingcap/check" "github.com/pingcap/parser" @@ -785,6 +786,8 @@ func (s *testPlanSuite) TestDAGPlanBuilderUnionScan(c *C) { } func (s *testPlanSuite) TestDAGPlanBuilderAgg(c *C) { + log.Println("TestDAGPlanBuilderAgg start") + defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) diff --git a/planner/core/physical_plans.go b/planner/core/physical_plans.go index 1dc1a7e5af237..124c01be8dfcc 100644 --- a/planner/core/physical_plans.go +++ b/planner/core/physical_plans.go @@ -115,7 +115,9 @@ type PhysicalIndexScan struct { // It is used for query feedback. Hist *statistics.Histogram - rangeDecidedBy []*expression.Column + rangeDecidedBy []*expression.Column + rangesWithFakeConds []*ranger.Range + keyOff2IdxOff []int // The index scan may be on a partition. isPartition bool diff --git a/util/ranger/types.go b/util/ranger/types.go index ceed9531b2e36..23f489d755459 100644 --- a/util/ranger/types.go +++ b/util/ranger/types.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/pingcap/errors" + "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" @@ -77,6 +78,81 @@ func (ran *Range) IsPoint(sc *stmtctx.StatementContext) bool { return !ran.LowExclude && !ran.HighExclude } +// StringWithJoinKeys formats the range with join keys and keyOff2IdxOff for index join in explain command +func (ran *Range) StringWithJoinKeys(keys []*expression.Column, keyOff2IdxOff []int) string { + maxIdxOff := -1 + for _, idxOff := range keyOff2IdxOff { + if maxIdxOff < idxOff { + maxIdxOff = idxOff + } + } + + idxOff2Key := make([]*expression.Column, maxIdxOff+1) + for keyOff, idxOff := range keyOff2IdxOff { + if idxOff >= 0 { + idxOff2Key[idxOff] = keys[keyOff] + } + } + + // resolve used keys and arrange keys by idxOff + var usedKeys []*expression.Column + for i := 0; i < len(idxOff2Key); i++ { + if i >= len(ran.LowVal) { + break + } + + key := idxOff2Key[i] + if key != nil { + usedKeys = append(usedKeys, key) + } + } + + if len(usedKeys) == len(ran.LowVal) { + return fmt.Sprintf("%v", usedKeys) + } + + lowStrs := make([]string, 0, len(ran.LowVal)) + for i, d := range ran.LowVal { + var key *expression.Column + if i < len(idxOff2Key) { + key = idxOff2Key[i] + } else { + key = nil + } + + if key == nil { + lowStrs = append(lowStrs, formatDatum(d, true)) + } else { + lowStrs = append(lowStrs, key.String()) + } + } + + highStrs := make([]string, 0, len(ran.HighVal)) + for i, d := range ran.HighVal { + var key *expression.Column + if i < len(idxOff2Key) { + key = idxOff2Key[i] + } else { + key = nil + } + + if key == nil { + highStrs = append(highStrs, formatDatum(d, false)) + } else { + highStrs = append(highStrs, key.String()) + } + } + + l, r := "[", "]" + if ran.LowExclude { + l = "(" + } + if ran.HighExclude { + r = ")" + } + return l + strings.Join(lowStrs, " ") + "," + strings.Join(highStrs, " ") + r +} + // String implements the Stringer interface. func (ran *Range) String() string { lowStrs := make([]string, 0, len(ran.LowVal))