From f343ffef6d9a0f3afd1d03491c8809e3506d09d8 Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Tue, 11 Oct 2022 18:53:50 +0800 Subject: [PATCH] planner: show operator cost formulas under 'explain format=true_card_cost' (#38405) ref pingcap/tidb#36243 --- planner/core/common_plans.go | 26 +++++++++++---- planner/core/plan_cost_ver2.go | 50 +++++++++++++++++------------ planner/core/plan_cost_ver2_test.go | 22 +++++++++++++ planner/core/planbuilder.go | 4 +++ 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index 438692d697e0a..d413c793b11dd 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -666,12 +666,14 @@ func (e *Explain) prepareSchema() error { switch { case (format == types.ExplainFormatROW || format == types.ExplainFormatBrief) && (!e.Analyze && e.RuntimeStatsColl == nil): fieldNames = []string{"id", "estRows", "task", "access object", "operator info"} - case format == types.ExplainFormatVerbose || format == types.ExplainFormatTrueCardCost: + case format == types.ExplainFormatVerbose: if e.Analyze || e.RuntimeStatsColl != nil { fieldNames = []string{"id", "estRows", "estCost", "actRows", "task", "access object", "execution info", "operator info", "memory", "disk"} } else { fieldNames = []string{"id", "estRows", "estCost", "task", "access object", "operator info"} } + case format == types.ExplainFormatTrueCardCost: + fieldNames = []string{"id", "estRows", "estCost", "costFormula", "actRows", "task", "access object", "execution info", "operator info", "memory", "disk"} case (format == types.ExplainFormatROW || format == types.ExplainFormatBrief) && (e.Analyze || e.RuntimeStatsColl != nil): fieldNames = []string{"id", "estRows", "actRows", "task", "access object", "execution info", "operator info", "memory", "disk"} case format == types.ExplainFormatDOT: @@ -850,7 +852,7 @@ func (e *Explain) prepareOperatorInfo(p Plan, taskType, id string) { return } - estRows, estCost, accessObject, operatorInfo := e.getOperatorInfo(p, id) + estRows, estCost, costFormula, accessObject, operatorInfo := e.getOperatorInfo(p, id) var row []string if e.Analyze || e.RuntimeStatsColl != nil { @@ -858,6 +860,9 @@ func (e *Explain) prepareOperatorInfo(p Plan, taskType, id string) { if strings.ToLower(e.Format) == types.ExplainFormatVerbose || strings.ToLower(e.Format) == types.ExplainFormatTrueCardCost { row = append(row, estCost) } + if strings.ToLower(e.Format) == types.ExplainFormatTrueCardCost { + row = append(row, costFormula) + } actRows, analyzeInfo, memoryInfo, diskInfo := getRuntimeInfoStr(e.ctx, p, e.RuntimeStatsColl) row = append(row, actRows, taskType, accessObject, analyzeInfo, operatorInfo, memoryInfo, diskInfo) } else { @@ -870,14 +875,14 @@ func (e *Explain) prepareOperatorInfo(p Plan, taskType, id string) { e.Rows = append(e.Rows, row) } -func (e *Explain) getOperatorInfo(p Plan, id string) (string, string, string, string) { +func (e *Explain) getOperatorInfo(p Plan, id string) (string, string, string, string, string) { // For `explain for connection` statement, `e.ExplainRows` will be set. for _, row := range e.ExplainRows { if len(row) < 5 { panic("should never happen") } if row[0] == id { - return row[1], "N/A", row[3], row[4] + return row[1], "N/A", "N/A", row[3], row[4] } } estRows := "N/A" @@ -885,9 +890,16 @@ func (e *Explain) getOperatorInfo(p Plan, id string) (string, string, string, st estRows = strconv.FormatFloat(si.RowCount, 'f', 2, 64) } estCost := "N/A" + costFormula := "N/A" if pp, ok := p.(PhysicalPlan); ok { - planCost, _ := getPlanCost(pp, property.RootTaskType, NewDefaultPlanCostOption()) - estCost = strconv.FormatFloat(planCost, 'f', 2, 64) + if e.ctx != nil && e.ctx.GetSessionVars().CostModelVersion == modelVer2 { + costVer2, _ := pp.getPlanCostVer2(property.RootTaskType, NewDefaultPlanCostOption()) + estCost = strconv.FormatFloat(costVer2.cost, 'f', 2, 64) + costFormula = costVer2.formula + } else { + planCost, _ := getPlanCost(pp, property.RootTaskType, NewDefaultPlanCostOption()) + estCost = strconv.FormatFloat(planCost, 'f', 2, 64) + } } var accessObject, operatorInfo string if plan, ok := p.(dataAccesser); ok { @@ -899,7 +911,7 @@ func (e *Explain) getOperatorInfo(p Plan, id string) (string, string, string, st } operatorInfo = p.ExplainInfo() } - return estRows, estCost, accessObject, operatorInfo + return estRows, estCost, costFormula, accessObject, operatorInfo } // BinaryPlanStrFromFlatPlan generates the compressed and encoded binary plan from a FlatPhysicalPlan. diff --git a/planner/core/plan_cost_ver2.go b/planner/core/plan_cost_ver2.go index 966844447416c..9d1fc4416ac18 100644 --- a/planner/core/plan_cost_ver2.go +++ b/planner/core/plan_cost_ver2.go @@ -54,7 +54,7 @@ func (p *basePhysicalPlan) getPlanCostVer2(taskType property.TaskType, option *P p.planCostVer2 = sumCostVer2(childCosts...) } p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -76,7 +76,7 @@ func (p *PhysicalSelection) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = sumCostVer2(filterCost, childCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -100,7 +100,7 @@ func (p *PhysicalProjection) getPlanCostVer2(taskType property.TaskType, option p.planCostVer2 = sumCostVer2(childCost, divCostVer2(projCost, concurrency)) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -117,7 +117,7 @@ func (p *PhysicalIndexScan) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = scanCostVer2(option, rows, rowSize, scanFactor) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -140,7 +140,7 @@ func (p *PhysicalTableScan) getPlanCostVer2(taskType property.TaskType, option * } p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -168,7 +168,7 @@ func (p *PhysicalIndexReader) getPlanCostVer2(taskType property.TaskType, option p.planCostVer2 = divCostVer2(sumCostVer2(childCost, netCost, seekCost), concurrency) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -203,7 +203,7 @@ func (p *PhysicalTableReader) getPlanCostVer2(taskType property.TaskType, option !hasCostFlag(option.CostFlag, CostFlagRecalculate) { // show the real cost in explain-statements p.planCostVer2 = divCostVer2(p.planCostVer2, 1000000000) } - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -259,7 +259,7 @@ func (p *PhysicalIndexLookUpReader) getPlanCostVer2(taskType property.TaskType, p.planCostVer2 = sumCostVer2(indexSideCost, divCostVer2(sumCostVer2(tableSideCost, doubleReadCost), doubleReadConcurrency)) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -307,7 +307,7 @@ func (p *PhysicalIndexMergeReader) getPlanCostVer2(taskType property.TaskType, o p.planCostVer2 = sumCostVer2(tableSideCost, sumIndexSideCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -362,7 +362,7 @@ func (p *PhysicalSort) getPlanCostVer2(taskType property.TaskType, option *PlanC p.planCostVer2 = sumCostVer2(childCost, sortCPUCost, sortMemCost, sortDiskCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -394,7 +394,7 @@ func (p *PhysicalTopN) getPlanCostVer2(taskType property.TaskType, option *PlanC p.planCostVer2 = sumCostVer2(childCost, topNCPUCost, topNMemCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -417,7 +417,7 @@ func (p *PhysicalStreamAgg) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = sumCostVer2(childCost, aggCost, groupCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -446,7 +446,7 @@ func (p *PhysicalHashAgg) getPlanCostVer2(taskType property.TaskType, option *Pl p.planCostVer2 = sumCostVer2(childCost, divCostVer2(sumCostVer2(aggCost, groupCost, hashBuildCost, hashProbeCost), concurrency)) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -476,7 +476,7 @@ func (p *PhysicalMergeJoin) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = sumCostVer2(leftChildCost, rightChildCost, filterCost, groupCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -520,7 +520,7 @@ func (p *PhysicalHashJoin) getPlanCostVer2(taskType property.TaskType, option *P p.planCostVer2 = sumCostVer2(buildChildCost, probeChildCost, buildHashCost, buildFilterCost, divCostVer2(sumCostVer2(probeFilterCost, probeHashCost), concurrency)) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -560,7 +560,7 @@ func (p *PhysicalIndexJoin) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = sumCostVer2(buildChildCost, buildFilterCost, divCostVer2(sumCostVer2(probeCost, probeFilterCost), probeConcurrency)) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } func (p *PhysicalIndexHashJoin) getPlanCostVer2(taskType property.TaskType, option *PlanCostOption) (costVer2, error) { @@ -601,7 +601,7 @@ func (p *PhysicalApply) getPlanCostVer2(taskType property.TaskType, option *Plan p.planCostVer2 = sumCostVer2(buildChildCost, buildFilterCost, probeCost, probeFilterCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 calculates the cost of the plan if it has not been calculated yet and returns the cost. @@ -622,7 +622,7 @@ func (p *PhysicalUnionAll) getPlanCostVer2(taskType property.TaskType, option *P } p.planCostVer2 = divCostVer2(sumCostVer2(childCosts...), concurrency) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -644,7 +644,7 @@ func (p *PhysicalExchangeReceiver) getPlanCostVer2(taskType property.TaskType, o p.planCostVer2 = sumCostVer2(childCost, netCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -668,7 +668,7 @@ func (p *PointGetPlan) getPlanCostVer2(taskType property.TaskType, option *PlanC p.planCostVer2 = sumCostVer2(netCost, seekCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } // getPlanCostVer2 returns the plan-cost of this sub-plan, which is: @@ -693,7 +693,7 @@ func (p *BatchPointGetPlan) getPlanCostVer2(taskType property.TaskType, option * p.planCostVer2 = sumCostVer2(netCost, seekCost) p.planCostInit = true - return p.planCostVer2, nil + return p.planCostVer2.label(p), nil } func scanCostVer2(option *PlanCostOption, rows, rowSize float64, scanFactor costVer2Factor) costVer2 { @@ -929,6 +929,14 @@ type costVer2 struct { formula string // It used to trace the cost calculation. } +func (c costVer2) label(p PhysicalPlan) costVer2 { + if !c.trace { + return c + } + c.formula = p.ExplainID().String() + return c +} + func traceCost(option *PlanCostOption) bool { if option != nil && hasCostFlag(option.CostFlag, CostFlagTrace) { return true diff --git a/planner/core/plan_cost_ver2_test.go b/planner/core/plan_cost_ver2_test.go index 109d27932fd6a..3a93a90420eaf 100644 --- a/planner/core/plan_cost_ver2_test.go +++ b/planner/core/plan_cost_ver2_test.go @@ -129,6 +129,28 @@ func TestCostModelVer2(t *testing.T) { } } +func TestCostModelShowFormula(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec(`create table t (a int)`) + tk.MustExec("insert into t values (1), (2), (3)") + tk.MustExec("set @@tidb_cost_model_version=2") + + tk.MustExecToErr("explain format='true_card_cost' select * from t") // 'true_card_cost' must work with 'explain analyze' + plan := tk.MustQuery("explain analyze format='true_card_cost' select * from t where a<3").Rows() + actual := make([][]interface{}, 0, len(plan)) + for _, row := range plan { + actual = append(actual, []interface{}{row[0], row[3]}) // id,costFormula + fmt.Println(actual) + } + require.Equal(t, actual, [][]interface{}{ + {"TableReader_7", "((Selection_6) + (net(2*rowsize(16)*tidb_kv_net_factor(8))) + (seek(tasks(20)*tidb_request_factor(9.5e+06))))/15"}, + {"└─Selection_6", "(cpu(3*filters(1)*tikv_cpu_factor(30))) + (TableFullScan_5)"}, + {" └─TableFullScan_5", "scan(3*logrowsize(29)*tikv_scan_factor(100))"}, + }) +} + func TestCostModelTraceVer2(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index fdc16e48d9f65..39bae869f68c5 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -4641,6 +4641,10 @@ func (b *PlanBuilder) buildTrace(trace *ast.TraceStmt) (Plan, error) { } func (b *PlanBuilder) buildExplainPlan(targetPlan Plan, format string, explainRows [][]string, analyze bool, execStmt ast.StmtNode, runtimeStats *execdetails.RuntimeStatsColl) (Plan, error) { + if strings.ToLower(format) == types.ExplainFormatTrueCardCost && !analyze { + return nil, errors.Errorf("'explain format=%v' cannot work without 'analyze', please use 'explain analyze format=%v'", format, format) + } + p := &Explain{ TargetPlan: targetPlan, Format: format,