diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index 19e6ac3d7e..3c0024928e 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -5304,6 +5304,23 @@ func TestIndexJoinCost(t *testing.T) { ` ├─Selection_9(Build) 1.25 0.00 cop[tikv] not(isnull(test.t_inner_idx.a))`, ` │ └─IndexRangeScan_7 1.25 0.00 cop[tikv] table:t_inner_idx, index:a(a) range: decided by [eq(test.t_inner_idx.a, test.t_outer.a)], keep order:false, stats:pseudo`, ` └─TableRowIDScan_8(Probe) 1.25 0.00 cop[tikv] table:t_inner_idx keep order:false, stats:pseudo`)) + + tk.MustQuery("explain format=verbose select /*+ inl_hash_join(t_outer, t_inner_idx) */ t_inner_idx.a from t_outer, t_inner_idx where t_outer.a=t_inner_idx.a").Check(testkit.Rows( + `IndexHashJoin_12 12487.50 221872.19 root inner join, inner:IndexReader_9, outer key:test.t_outer.a, inner key:test.t_inner_idx.a, equal cond:eq(test.t_outer.a, test.t_inner_idx.a)`, + `├─TableReader_20(Build) 9990.00 36412.58 root data:Selection_19`, + `│ └─Selection_19 9990.00 465000.00 cop[tikv] not(isnull(test.t_outer.a))`, + `│ └─TableFullScan_18 10000.00 435000.00 cop[tikv] table:t_outer keep order:false, stats:pseudo`, + `└─IndexReader_9(Probe) 1.25 4.56 root index:Selection_8`, + ` └─Selection_8 1.25 0.00 cop[tikv] not(isnull(test.t_inner_idx.a))`, + ` └─IndexRangeScan_7 1.25 0.00 cop[tikv] table:t_inner_idx, index:a(a) range: decided by [eq(test.t_inner_idx.a, test.t_outer.a)], keep order:false, stats:pseudo`)) + tk.MustQuery("explain format=verbose select /*+ inl_merge_join(t_outer, t_inner_idx) */ t_inner_idx.a from t_outer, t_inner_idx where t_outer.a=t_inner_idx.a").Check(testkit.Rows( + `IndexMergeJoin_17 12487.50 215890.68 root inner join, inner:IndexReader_15, outer key:test.t_outer.a, inner key:test.t_inner_idx.a`, + `├─TableReader_20(Build) 9990.00 36412.58 root data:Selection_19`, + `│ └─Selection_19 9990.00 465000.00 cop[tikv] not(isnull(test.t_outer.a))`, + `│ └─TableFullScan_18 10000.00 435000.00 cop[tikv] table:t_outer keep order:false, stats:pseudo`, + `└─IndexReader_15(Probe) 1.25 4.56 root index:Selection_14`, + ` └─Selection_14 1.25 0.00 cop[tikv] not(isnull(test.t_inner_idx.a))`, + ` └─IndexRangeScan_13 1.25 0.00 cop[tikv] table:t_inner_idx, index:a(a) range: decided by [eq(test.t_inner_idx.a, test.t_outer.a)], keep order:true, stats:pseudo`)) } func TestHeuristicIndexSelection(t *testing.T) { diff --git a/planner/core/task.go b/planner/core/task.go index 6bf9f6ac72..37fc36c114 100644 --- a/planner/core/task.go +++ b/planner/core/task.go @@ -295,16 +295,15 @@ func (p *PhysicalIndexMergeJoin) attach2Task(tasks ...task) task { } t := &rootTask{ p: p, - cst: p.GetCost(outerTask, innerTask), + cst: p.GetCost(outerTask.count(), innerTask.count(), outerTask.cost(), innerTask.cost()), } p.cost = t.cost() return t } // GetCost computes the cost of index merge join operator and its children. -func (p *PhysicalIndexMergeJoin) GetCost(outerTask, innerTask task) float64 { +func (p *PhysicalIndexMergeJoin) GetCost(outerCnt, innerCnt, outerCost, innerCost float64) float64 { var cpuCost float64 - outerCnt, innerCnt := outerTask.count(), innerTask.count() sessVars := p.ctx.GetSessionVars() // Add the cost of evaluating outer filter, since inner filter of index join // is always empty, we can simply tell whether outer filter is empty using the @@ -360,8 +359,8 @@ func (p *PhysicalIndexMergeJoin) GetCost(outerTask, innerTask task) float64 { // So the memory cost consider the results size for each batch. memoryCost := innerConcurrency * (batchSize * avgProbeCnt) * sessVars.MemoryFactor - innerPlanCost := outerCnt * innerTask.cost() - return outerTask.cost() + innerPlanCost + cpuCost + memoryCost + innerPlanCost := outerCnt * innerCost + return outerCost + innerPlanCost + cpuCost + memoryCost } func (p *PhysicalIndexHashJoin) attach2Task(tasks ...task) task { @@ -374,16 +373,15 @@ func (p *PhysicalIndexHashJoin) attach2Task(tasks ...task) task { } t := &rootTask{ p: p, - cst: p.GetCost(outerTask, innerTask), + cst: p.GetCost(outerTask.count(), innerTask.count(), outerTask.cost(), innerTask.cost()), } p.cost = t.cost() return t } // GetCost computes the cost of index merge join operator and its children. -func (p *PhysicalIndexHashJoin) GetCost(outerTask, innerTask task) float64 { +func (p *PhysicalIndexHashJoin) GetCost(outerCnt, innerCnt, outerCost, innerCost float64) float64 { var cpuCost float64 - outerCnt, innerCnt := outerTask.count(), innerTask.count() sessVars := p.ctx.GetSessionVars() // Add the cost of evaluating outer filter, since inner filter of index join // is always empty, we can simply tell whether outer filter is empty using the @@ -437,8 +435,8 @@ func (p *PhysicalIndexHashJoin) GetCost(outerTask, innerTask task) float64 { // since the executor is pipelined and not all workers are always in full load. memoryCost := concurrency * (batchSize * distinctFactor) * innerCnt * sessVars.MemoryFactor // Cost of inner child plan, i.e, mainly I/O and network cost. - innerPlanCost := outerCnt * innerTask.cost() - return outerTask.cost() + innerPlanCost + cpuCost + memoryCost + innerPlanCost := outerCnt * innerCost + return outerCost + innerPlanCost + cpuCost + memoryCost } func (p *PhysicalIndexJoin) attach2Task(tasks ...task) task {