diff --git a/pkg/planner/core/task.go b/pkg/planner/core/task.go index 966d4ec4172c0..62e0d53c172ee 100644 --- a/pkg/planner/core/task.go +++ b/pkg/planner/core/task.go @@ -875,10 +875,12 @@ func (p *PhysicalLimit) attach2Task(tasks ...task) task { } else if !cop.idxMergeIsIntersection { // We only support push part of the order prop down to index merge build case. if len(cop.rootTaskConds) == 0 { - if cop.indexPlanFinished { - // when the index plan is finished, sink the limit to the index merge table side. + // For double read which requires order being kept, the limit cannot be pushed down to the table side, + // because handles would be reordered before being sent to table scan. + if cop.indexPlanFinished && !cop.keepOrder { + // when the index plan is finished and index plan is not ordered, sink the limit to the index merge table side. suspendLimitAboveTablePlan() - } else { + } else if !cop.indexPlanFinished { // cop.indexPlanFinished = false indicates the table side is a pure table-scan, sink the limit to the index merge index side. newCount := p.Offset + p.Count limitChildren := make([]PhysicalPlan, 0, len(cop.idxMergePartPlans)) @@ -893,6 +895,10 @@ func (p *PhysicalLimit) attach2Task(tasks ...task) task { cop.idxMergePartPlans = limitChildren t = cop.convertToRootTask(p.SCtx()) sunk = p.sinkIntoIndexMerge(t) + } else { + // when there are some limitations, just sink the limit upon the index merge reader. + t = cop.convertToRootTask(p.SCtx()) + sunk = p.sinkIntoIndexMerge(t) } } else { // when there are some root conditions, just sink the limit upon the index merge reader. diff --git a/tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result b/tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result index e50d6a0705f3a..52d57aab7c90e 100644 --- a/tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result +++ b/tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result @@ -3728,3 +3728,44 @@ IndexMerge_20 1.00 root type: union, limit embedded(offset:0, count:1) └─TableRowIDScan_13(Probe) 1.00 cop[tikv] table:t3 keep order:false, stats:pseudo show warnings; Level Code Message +CREATE TABLE `tbl_43` ( +`col_304` binary(207) NOT NULL DEFAULT 'eIenHx\0\0\0\0\0\0\0\0\0\0\0\0', +PRIMARY KEY (`col_304`) /*T![clustered_index] CLUSTERED */, +UNIQUE KEY `idx_259` (`col_304`(5)), +UNIQUE KEY `idx_260` (`col_304`(2)), +KEY `idx_261` (`col_304`), +UNIQUE KEY `idx_262` (`col_304`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +insert into tbl_43 values("BCmuENPHzSOIMJLPB"),("LDOdXZYpOR"),("R"),("TloTqcHhdgpwvMsSoJ"),("UajN"),("mAwLZbiyq"),("swLIoWa"); +explain format = 'brief' select min(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +id estRows task access object operator info +StreamAgg 1.00 root funcs:min(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304)->Column#2 +└─Limit 1.00 root offset:0, count:1 + └─IndexMerge 1.00 root type: union + ├─Selection(Build) 0.00 cop[tikv] 1 + │ └─TableRangeScan 0.00 cop[tikv] table:tbl_43 range:["LUBGzGMA","LUBGzGMA"], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) 0.42 cop[tikv] table:tbl_43, index:idx_261(col_304) range:[-inf,"YEpfYfPVvhMlHGHSMKm"), keep order:true, stats:pseudo + ├─IndexRangeScan(Build) 0.42 cop[tikv] table:tbl_43, index:idx_262(col_304) range:("PE",+inf], keep order:true, stats:pseudo + ├─TableRangeScan(Build) 0.42 cop[tikv] table:tbl_43 range:[-inf,"MFWmuOsoyDv"), keep order:true, stats:pseudo + ├─TableRangeScan(Build) 0.42 cop[tikv] table:tbl_43 range:("TSeMYpDXnFIyp",+inf], keep order:true, stats:pseudo + └─Selection(Probe) 1.00 cop[tikv] or(or(lt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "YEpfYfPVvhMlHGHSMKm"), gt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "PE")), or(and(eq(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "LUBGzGMA"), 1), or(lt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "MFWmuOsoyDv"), gt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "TSeMYpDXnFIyp")))) + └─TableRowIDScan 1.25 cop[tikv] table:tbl_43 keep order:false, stats:pseudo +select min(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +min(col_304) +BCmuENPHzSOIMJLPB +explain format = 'brief' select max(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +id estRows task access object operator info +StreamAgg 1.00 root funcs:max(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304)->Column#2 +└─Limit 1.00 root offset:0, count:1 + └─IndexMerge 1.00 root type: union + ├─Selection(Build) 0.00 cop[tikv] 1 + │ └─TableRangeScan 0.00 cop[tikv] table:tbl_43 range:["LUBGzGMA","LUBGzGMA"], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) 0.42 cop[tikv] table:tbl_43, index:idx_261(col_304) range:[-inf,"YEpfYfPVvhMlHGHSMKm"), keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) 0.42 cop[tikv] table:tbl_43, index:idx_262(col_304) range:("PE",+inf], keep order:true, desc, stats:pseudo + ├─TableRangeScan(Build) 0.42 cop[tikv] table:tbl_43 range:[-inf,"MFWmuOsoyDv"), keep order:true, desc, stats:pseudo + ├─TableRangeScan(Build) 0.42 cop[tikv] table:tbl_43 range:("TSeMYpDXnFIyp",+inf], keep order:true, desc, stats:pseudo + └─Selection(Probe) 1.00 cop[tikv] or(or(lt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "YEpfYfPVvhMlHGHSMKm"), gt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "PE")), or(and(eq(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "LUBGzGMA"), 1), or(lt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "MFWmuOsoyDv"), gt(planner__core__casetest__physicalplantest__physical_plan.tbl_43.col_304, "TSeMYpDXnFIyp")))) + └─TableRowIDScan 1.25 cop[tikv] table:tbl_43 keep order:false, stats:pseudo +select max(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +max(col_304) +swLIoWa diff --git a/tests/integrationtest/t/planner/core/casetest/physicalplantest/physical_plan.test b/tests/integrationtest/t/planner/core/casetest/physicalplantest/physical_plan.test index 3e28f72db0e17..ae30fe07ab250 100644 --- a/tests/integrationtest/t/planner/core/casetest/physicalplantest/physical_plan.test +++ b/tests/integrationtest/t/planner/core/casetest/physicalplantest/physical_plan.test @@ -992,3 +992,17 @@ set tidb_cost_model_version=DEFAULT; explain select /*+ USE_INDEX_MERGE(t3, aid_c1, aid_c2) */ * from t3 where (aid = 1 and c1='aaa') or (aid = 1 and c2='bbb') limit 1; show warnings; +# TestIndexMergeIssue52947 +CREATE TABLE `tbl_43` ( + `col_304` binary(207) NOT NULL DEFAULT 'eIenHx\0\0\0\0\0\0\0\0\0\0\0\0', + PRIMARY KEY (`col_304`) /*T![clustered_index] CLUSTERED */, + UNIQUE KEY `idx_259` (`col_304`(5)), + UNIQUE KEY `idx_260` (`col_304`(2)), + KEY `idx_261` (`col_304`), + UNIQUE KEY `idx_262` (`col_304`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +insert into tbl_43 values("BCmuENPHzSOIMJLPB"),("LDOdXZYpOR"),("R"),("TloTqcHhdgpwvMsSoJ"),("UajN"),("mAwLZbiyq"),("swLIoWa"); +explain format = 'brief' select min(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +select min(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +explain format = 'brief' select max(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; +select max(col_304) from (select /*+ use_index_merge( tbl_43 ) */ * from tbl_43 where not( tbl_43.col_304 between 'YEpfYfPVvhMlHGHSMKm' and 'PE' ) or tbl_43.col_304 in ( 'LUBGzGMA' ) and tbl_43.col_304 between 'HpsjfuSReCwBoh' and 'fta' or not( tbl_43.col_304 between 'MFWmuOsoyDv' and 'TSeMYpDXnFIyp' ) order by col_304) x; \ No newline at end of file