diff --git a/cmd/explaintest/r/explain_easy.result b/cmd/explaintest/r/explain_easy.result index d7f96e14d22ae..b737fada05f0e 100644 --- a/cmd/explaintest/r/explain_easy.result +++ b/cmd/explaintest/r/explain_easy.result @@ -132,12 +132,11 @@ Projection 10000.00 root eq(test.t1.c2, test.t2.c2)->Column#11 └─Apply 10000.00 root CARTESIAN left outer join ├─TableReader(Build) 10000.00 root data:TableFullScan │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo - └─Limit(Probe) 10000.00 root offset:0, count:1 - └─Projection 10000.00 root test.t2.c1, test.t2.c2 - └─IndexLookUp 10000.00 root - ├─Limit(Build) 10000.00 cop[tikv] offset:0, count:1 - │ └─IndexRangeScan 10000.00 cop[tikv] table:t2, index:c1(c1) range: decided by [eq(test.t1.c1, test.t2.c1)], keep order:true, stats:pseudo - └─TableRowIDScan(Probe) 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo + └─Projection(Probe) 10000.00 root test.t2.c1, test.t2.c2 + └─IndexLookUp 10000.00 root limit embedded(offset:0, count:1) + ├─Limit(Build) 10000.00 cop[tikv] offset:0, count:1 + │ └─IndexRangeScan 10000.00 cop[tikv] table:t2, index:c1(c1) range: decided by [eq(test.t1.c1, test.t2.c1)], keep order:true, stats:pseudo + └─TableRowIDScan(Probe) 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo explain format = 'brief' select * from t1 order by c1 desc limit 1; id estRows task access object operator info Limit 1.00 root offset:0, count:1 diff --git a/planner/core/casetest/testdata/integration_suite_out.json b/planner/core/casetest/testdata/integration_suite_out.json index 062219d5ca773..5e1a84ac99ae4 100644 --- a/planner/core/casetest/testdata/integration_suite_out.json +++ b/planner/core/casetest/testdata/integration_suite_out.json @@ -14,12 +14,11 @@ { "SQL": "explain format = 'brief' select * from tbl use index(idx_b_c) where b > 1 order by b desc limit 2,1", "Plan": [ - "Limit 1.00 root offset:2, count:1", - "└─Projection 3.00 root test.tbl.a, test.tbl.b, test.tbl.c", - " └─IndexLookUp 3.00 root ", - " ├─Limit(Build) 3.00 cop[tikv] offset:0, count:3", - " │ └─IndexRangeScan 3.00 cop[tikv] table:tbl, index:idx_b_c(b, c) range:(1,+inf], keep order:true, desc", - " └─TableRowIDScan(Probe) 3.00 cop[tikv] table:tbl keep order:false" + "Projection 1.00 root test.tbl.a, test.tbl.b, test.tbl.c", + "└─IndexLookUp 1.00 root limit embedded(offset:2, count:1)", + " ├─Limit(Build) 3.00 cop[tikv] offset:0, count:3", + " │ └─IndexRangeScan 3.00 cop[tikv] table:tbl, index:idx_b_c(b, c) range:(1,+inf], keep order:true, desc", + " └─TableRowIDScan(Probe) 1.00 cop[tikv] table:tbl keep order:false" ] }, { @@ -4329,12 +4328,11 @@ { "SQL": "explain select /*+ order_index(t1, idx_a) */ * from t1 where a<10 order by a limit 1;", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_17 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_16 1.00 root ", - " ├─Limit_15(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_17 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_16 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_15(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, @@ -4444,48 +4442,44 @@ { "SQL": "explain select /*+ order_index(t1, idx_a) use_index(t1, idx_a) */ * from t1 where a<10 order by a limit 1;", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_19 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_18 1.00 root ", - " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_19 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_18 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, { "SQL": "explain select /*+ order_index(t1, idx_a) */ * from t1 use index(idx_a) where a<10 order by a limit 1;", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_19 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_18 1.00 root ", - " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_19 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_18 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, { "SQL": "explain select /*+ order_index(t1, idx_a) force_index(t1, idx_a) */ * from t1 where a<10 order by a limit 1;", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_19 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_18 1.00 root ", - " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_19 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_18 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, { "SQL": "explain select /*+ order_index(t1, idx_a) */ * from t1 force index(idx_a) where a<10 order by a limit 1;", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_19 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_18 1.00 root ", - " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_19 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_18 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, @@ -4658,12 +4652,11 @@ { "SQL": "explain select /*+ qb_name(qb, v) order_index(t1@qb, idx_a) */ * from v", "Plan": [ - "Limit_14 1.00 root offset:0, count:1", - "└─Projection_19 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_18 1.00 root ", - " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_15 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_16(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + "Projection_19 1.00 root test.t1.a, test.t1.b", + "└─IndexLookUp_18 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_17(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_15 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_16(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, @@ -4701,20 +4694,19 @@ { "SQL": "explain WITH CTE AS (select /*+ order_index(t1, idx_a) */ * from t1 where a<10 order by a limit 1) SELECT * FROM CTE WHERE CTE.a <18 union select * from cte where cte.b > 1;", "Plan": [ - "HashAgg_30 2.00 root group by:Column#8, Column#9, funcs:firstrow(Column#8)->Column#8, funcs:firstrow(Column#9)->Column#9", - "└─Union_31 1.28 root ", - " ├─Selection_33 0.64 root lt(test.t1.a, 18)", - " │ └─CTEFullScan_34 0.80 root CTE:cte data:CTE_0", - " └─Selection_36 0.64 root gt(test.t1.b, 1)", - " └─CTEFullScan_37 0.80 root CTE:cte data:CTE_0", + "HashAgg_31 2.00 root group by:Column#8, Column#9, funcs:firstrow(Column#8)->Column#8, funcs:firstrow(Column#9)->Column#9", + "└─Union_32 1.28 root ", + " ├─Selection_34 0.64 root lt(test.t1.a, 18)", + " │ └─CTEFullScan_35 0.80 root CTE:cte data:CTE_0", + " └─Selection_37 0.64 root gt(test.t1.b, 1)", + " └─CTEFullScan_38 0.80 root CTE:cte data:CTE_0", "CTE_0 0.80 root Non-Recursive CTE", "└─Selection_18(Seed Part) 0.80 root or(lt(test.t1.a, 18), gt(test.t1.b, 1))", - " └─Limit_24 1.00 root offset:0, count:1", - " └─Projection_29 1.00 root test.t1.a, test.t1.b", - " └─IndexLookUp_28 1.00 root ", - " ├─Limit_27(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_25 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", - " └─TableRowIDScan_26(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + " └─Projection_29 1.00 root test.t1.a, test.t1.b", + " └─IndexLookUp_28 1.00 root limit embedded(offset:0, count:1)", + " ├─Limit_27(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_25 1.00 cop[tikv] table:t1, index:idx_a(a) range:[-inf,10), keep order:true, stats:pseudo", + " └─TableRowIDScan_26(Probe) 1.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warn": null }, @@ -4816,12 +4808,11 @@ { "SQL": "explain select /*+ order_index(thh, a) */ * from thh where a<1 order by a limit 1", "Plan": [ - "Limit_12 1.00 root offset:0, count:1", - "└─Projection_17 1.00 root test.thh.a, test.thh.b", - " └─IndexLookUp_16 1.00 root partition:all ", - " ├─Limit_15(Build) 1.00 cop[tikv] offset:0, count:1", - " │ └─IndexRangeScan_13 1.00 cop[tikv] table:thh, index:a(a) range:[-inf,1), keep order:true, stats:pseudo", - " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:thh keep order:false, stats:pseudo" + "Projection_17 1.00 root test.thh.a, test.thh.b", + "└─IndexLookUp_16 1.00 root partition:all limit embedded(offset:0, count:1)", + " ├─Limit_15(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan_13 1.00 cop[tikv] table:thh, index:a(a) range:[-inf,1), keep order:true, stats:pseudo", + " └─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:thh keep order:false, stats:pseudo" ], "Warn": null } diff --git a/planner/core/issuetest/planner_issue_test.go b/planner/core/issuetest/planner_issue_test.go index 1994841e206c8..a98641ca3c2de 100644 --- a/planner/core/issuetest/planner_issue_test.go +++ b/planner/core/issuetest/planner_issue_test.go @@ -61,7 +61,16 @@ func TestIssue43461(t *testing.T) { require.NoError(t, err) require.NotNil(t, p) - idxLookUpPlan, ok := p.(*core.PhysicalLimit).Children()[0].(*core.PhysicalProjection).Children()[0].(*core.PhysicalIndexLookUpReader) + var idxLookUpPlan *core.PhysicalIndexLookUpReader + var ok bool + + for { + idxLookUpPlan, ok = p.(*core.PhysicalIndexLookUpReader) + if ok { + break + } + p = p.(core.PhysicalPlan).Children()[0] + } require.True(t, ok) is := idxLookUpPlan.IndexPlans[0].(*core.PhysicalIndexScan) diff --git a/planner/core/task.go b/planner/core/task.go index 7b618708ff6cb..2925785219591 100644 --- a/planner/core/task.go +++ b/planner/core/task.go @@ -897,20 +897,27 @@ func (p *PhysicalLimit) sinkIntoIndexLookUp(t task) bool { } } + // We can sink Limit into IndexLookUpReader only if tablePlan contains no Selection. + ts, isTableScan := reader.tablePlan.(*PhysicalTableScan) + if !isTableScan { + return false + } + // If this happens, some Projection Operator must be inlined into this Limit. (issues/14428) // For example, if the original plan is `IndexLookUp(col1, col2) -> Limit(col1, col2) -> Project(col1)`, // then after inlining the Project, it will be `IndexLookUp(col1, col2) -> Limit(col1)` here. // If the Limit is sunk into the IndexLookUp, the IndexLookUp's schema needs to be updated as well, - // but updating it here is not safe, so do not sink Limit into this IndexLookUp in this case now. + // So we add an extra projection to solve the problem. if p.Schema().Len() != reader.Schema().Len() { - return false + extraProj := PhysicalProjection{ + Exprs: expression.Column2Exprs(p.schema.Columns), + }.Init(p.SCtx(), p.statsInfo(), p.blockOffset, nil) + extraProj.SetSchema(p.schema) + // If the root.p is already a Projection. We left the optimization for the later Projection Elimination. + extraProj.SetChildren(root.p) + root.p = extraProj } - // We can sink Limit into IndexLookUpReader only if tablePlan contains no Selection. - ts, isTableScan := reader.tablePlan.(*PhysicalTableScan) - if !isTableScan { - return false - } reader.PushedLimit = &PushedDownLimit{ Offset: p.Offset, Count: p.Count,