From 29a0b0772def31b2eca6229bd47e5f01c9d1a49a Mon Sep 17 00:00:00 2001 From: nitao Date: Thu, 20 Jun 2024 20:39:00 +0800 Subject: [PATCH 1/5] fix some little bug of stats (#17035) fix some little bug of stats Approved by: @zhangxu19830126, @triump2020, @ouyuanning --- pkg/pb/statsinfo/statsinfo.go | 2 +- pkg/sql/plan/explain/explain_node.go | 2 +- pkg/sql/plan/stats.go | 1 + pkg/vm/engine/disttae/stats.go | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/pb/statsinfo/statsinfo.go b/pkg/pb/statsinfo/statsinfo.go index d7f16a2be3587..5267bc45e5195 100644 --- a/pkg/pb/statsinfo/statsinfo.go +++ b/pkg/pb/statsinfo/statsinfo.go @@ -20,7 +20,7 @@ func (sc *StatsInfo) NeedUpdate(currentApproxObjNum int64) bool { if sc.ApproxObjectNumber == 0 || sc.AccurateObjectNumber == 0 { return true } - if math.Abs(float64(sc.ApproxObjectNumber-currentApproxObjNum)) >= 10 { + if math.Abs(float64(sc.ApproxObjectNumber-currentApproxObjNum)) >= float64(sc.AccurateObjectNumber) { return true } if float64(currentApproxObjNum)/float64(sc.ApproxObjectNumber) > 1.05 || float64(currentApproxObjNum)/float64(sc.ApproxObjectNumber) < 0.95 { diff --git a/pkg/sql/plan/explain/explain_node.go b/pkg/sql/plan/explain/explain_node.go index d2b0619627957..74a4a9ed58e0d 100644 --- a/pkg/sql/plan/explain/explain_node.go +++ b/pkg/sql/plan/explain/explain_node.go @@ -989,7 +989,7 @@ func (c *CostDescribeImpl) GetDescription(ctx context.Context, options *ExplainO if c.Stats.BlockNum > 0 { blockNumStr = " blockNum=" + strconv.FormatInt(int64(c.Stats.BlockNum), 10) } - if c.Stats.HashmapStats != nil && c.Stats.HashmapStats.HashmapSize > 0 { + if c.Stats.HashmapStats != nil && c.Stats.HashmapStats.HashmapSize > 1 { hashmapSizeStr = " hashmapSize=" + strconv.FormatFloat(c.Stats.HashmapStats.HashmapSize, 'f', 2, 64) } buf.WriteString(" (cost=" + strconv.FormatFloat(c.Stats.Cost, 'f', 2, 64) + diff --git a/pkg/sql/plan/stats.go b/pkg/sql/plan/stats.go index f86b76c68a5ef..346c1c2c1fefa 100644 --- a/pkg/sql/plan/stats.go +++ b/pkg/sql/plan/stats.go @@ -867,6 +867,7 @@ func ReCalcNodeStats(nodeID int32, builder *QueryBuilder, recursive bool, leafNo node.Stats.Outcnt = childStats.Outcnt node.Stats.Cost = childStats.Outcnt node.Stats.Selectivity = childStats.Selectivity + node.Stats.BlockNum = childStats.BlockNum } } diff --git a/pkg/vm/engine/disttae/stats.go b/pkg/vm/engine/disttae/stats.go index e446208a3616a..7552e68b2c0ef 100644 --- a/pkg/vm/engine/disttae/stats.go +++ b/pkg/vm/engine/disttae/stats.go @@ -168,7 +168,7 @@ func (gs *GlobalStats) ShouldUpdate(key pb.StatsInfoKey, entryNum int64) bool { gs.mu.Lock() defer gs.mu.Unlock() info, ok := gs.mu.statsInfoMap[key] - if ok && info != nil && info.BlockNumber > entryNum { + if ok && info != nil && info.BlockNumber-entryNum > 64 { return false } return true From 4d6e9f36f7f4fbe660f3885478a66836fcc53d1d Mon Sep 17 00:00:00 2001 From: nitao Date: Thu, 30 May 2024 13:06:25 +0800 Subject: [PATCH 2/5] fix stats for runtime filter in some cases (#16491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix stats for runtime filter in some cases ___ ### **PR Type** Bug fix ___ ### **Description** - Added calls to `recalcStatsByRuntimeFilter` in various functions to ensure stats are recalculated after applying runtime filters. - Refactored `recalcStatsByRuntimeFilter` to use `builder` for selectivity calculation instead of passing `runtimeFilterSel`. - Adjusted stats recalculation logic for scan and join nodes in `recalcStatsByRuntimeFilter`. ___ ### **Changes walkthrough** 📝
Relevant files
Bug fix
apply_indices.go
Add stats recalculation for runtime filter in applyIndicesForJoins

pkg/sql/plan/apply_indices.go
  • Added call to recalcStatsByRuntimeFilter after appending runtime
    filter to RuntimeFilterBuildList.
  • +1/-0     
    build_constraint_util.go
    Add stats recalculation for runtime filter in build constraint utility

    pkg/sql/plan/build_constraint_util.go
  • Added call to recalcStatsByRuntimeFilter in appendPrimaryConstrantPlan
    function.
  • +2/-0     
    build_dml_util.go
    Add stats recalculation for runtime filter in DML utility

    pkg/sql/plan/build_dml_util.go
  • Added call to recalcStatsByRuntimeFilter in appendDeleteIndexTablePlan
    function.
  • +1/-0     
    runtime_filter.go
    Update stats recalculation method calls in runtime filter

    pkg/sql/plan/runtime_filter.go
  • Modified calls to recalcStatsByRuntimeFilter to use builder instead of
    runtimeFilterSel.
  • +2/-3     
    stats.go
    Refactor stats recalculation logic in stats utility           

    pkg/sql/plan/stats.go
  • Refactored recalcStatsByRuntimeFilter to use builder for selectivity
    calculation.
  • Adjusted stats recalculation logic for scan and join nodes.
  • +11/-7   
    ___ > 💡 **PR-Agent usage**: >Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions Approved by: @ouyuanning --- pkg/sql/plan/apply_indices.go | 1 + pkg/sql/plan/build_constraint_util.go | 2 ++ pkg/sql/plan/build_dml_util.go | 1 + pkg/sql/plan/runtime_filter.go | 5 ++--- pkg/sql/plan/stats.go | 19 ++++++++++++------- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/sql/plan/apply_indices.go b/pkg/sql/plan/apply_indices.go index bb77756818a04..37157fb596afb 100644 --- a/pkg/sql/plan/apply_indices.go +++ b/pkg/sql/plan/apply_indices.go @@ -1001,6 +1001,7 @@ func (builder *QueryBuilder) applyIndicesForJoins(nodeID int32, node *plan.Node, }, builder.ctxByNode[nodeID]) node.RuntimeFilterBuildList = append(node.RuntimeFilterBuildList, MakeRuntimeFilter(rfTag, len(condIdx) < numParts, GetInFilterCardLimitOnPK(leftChild.Stats.TableCnt), rfBuildExpr)) + recalcStatsByRuntimeFilter(builder.qry.Nodes[idxTableNodeID], node, builder) pkIdx := leftChild.TableDef.Name2ColIndex[leftChild.TableDef.Pkey.PkeyColName] pkExpr := &plan.Expr{ diff --git a/pkg/sql/plan/build_constraint_util.go b/pkg/sql/plan/build_constraint_util.go index a3dc318478ccf..0d85ea5610935 100644 --- a/pkg/sql/plan/build_constraint_util.go +++ b/pkg/sql/plan/build_constraint_util.go @@ -1407,6 +1407,7 @@ func appendPrimaryConstraintPlan( }, } fuzzyFilterNode.RuntimeFilterBuildList = []*plan.RuntimeFilterSpec{MakeRuntimeFilter(rfTag, false, GetInFilterCardLimitOnPK(scanNode.Stats.TableCnt), buildExpr)} + recalcStatsByRuntimeFilter(scanNode, fuzzyFilterNode, builder) } lastNodeId = builder.appendNode(fuzzyFilterNode, bindCtx) @@ -1527,6 +1528,7 @@ func appendPrimaryConstraintPlan( RuntimeFilterBuildList: []*plan.RuntimeFilterSpec{MakeRuntimeFilter(rfTag, false, GetInFilterCardLimitOnPK(scanNode.Stats.TableCnt), buildExpr)}, } lastNodeId = builder.appendNode(joinNode, bindCtx) + recalcStatsByRuntimeFilter(scanNode, joinNode, builder) // append agg node. aggGroupBy := []*Expr{ diff --git a/pkg/sql/plan/build_dml_util.go b/pkg/sql/plan/build_dml_util.go index d6ad1b4544b26..9b0717c6545cd 100644 --- a/pkg/sql/plan/build_dml_util.go +++ b/pkg/sql/plan/build_dml_util.go @@ -3092,6 +3092,7 @@ func appendDeleteIndexTablePlan( ProjectList: projectList, RuntimeFilterBuildList: []*plan.RuntimeFilterSpec{MakeRuntimeFilter(rfTag, false, GetInFilterCardLimitOnPK(builder.qry.Nodes[leftId].Stats.TableCnt), buildExpr)}, }, bindCtx) + recalcStatsByRuntimeFilter(builder.qry.Nodes[leftId], builder.qry.Nodes[lastNodeId], builder) return lastNodeId, nil } diff --git a/pkg/sql/plan/runtime_filter.go b/pkg/sql/plan/runtime_filter.go index 37de441bfde76..e62e6a8d7cdc0 100644 --- a/pkg/sql/plan/runtime_filter.go +++ b/pkg/sql/plan/runtime_filter.go @@ -177,8 +177,7 @@ func (builder *QueryBuilder) generateRuntimeFilters(nodeID int32) { }, } node.RuntimeFilterBuildList = append(node.RuntimeFilterBuildList, MakeRuntimeFilter(rfTag, false, inLimit, buildExpr)) - - recalcStatsByRuntimeFilter(leftChild, node, rightChild.Stats.Selectivity) + recalcStatsByRuntimeFilter(leftChild, node, builder) return } @@ -255,5 +254,5 @@ func (builder *QueryBuilder) generateRuntimeFilters(nodeID int32) { buildExpr, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "serial", buildArgs) node.RuntimeFilterBuildList = append(node.RuntimeFilterBuildList, MakeRuntimeFilter(rfTag, cnt < len(tableDef.Pkey.Names), GetInFilterCardLimitOnPK(leftChild.Stats.TableCnt), buildExpr)) - recalcStatsByRuntimeFilter(leftChild, node, rightChild.Stats.Selectivity) + recalcStatsByRuntimeFilter(leftChild, node, builder) } diff --git a/pkg/sql/plan/stats.go b/pkg/sql/plan/stats.go index 346c1c2c1fefa..a787013bdf551 100644 --- a/pkg/sql/plan/stats.go +++ b/pkg/sql/plan/stats.go @@ -993,16 +993,21 @@ func foldTableScanFilters(proc *process.Process, qry *Query, nodeId int32) error return nil } -func recalcStatsByRuntimeFilter(node *plan.Node, joinNode *plan.Node, runtimeFilterSel float64) { - if node.NodeType != plan.Node_TABLE_SCAN { +func recalcStatsByRuntimeFilter(scanNode *plan.Node, joinNode *plan.Node, builder *QueryBuilder) { + if scanNode.NodeType != plan.Node_TABLE_SCAN { return } - node.Stats.Cost *= runtimeFilterSel - node.Stats.Outcnt *= runtimeFilterSel - if node.Stats.Cost < 1 { - node.Stats.Cost = 1 + if joinNode.NodeType != plan.Node_JOIN && joinNode.NodeType != plan.Node_FUZZY_FILTER { + return + } + runtimeFilterSel := builder.qry.Nodes[joinNode.Children[1]].Stats.Selectivity + scanNode.Stats.Cost *= runtimeFilterSel + scanNode.Stats.Outcnt *= runtimeFilterSel + if scanNode.Stats.Cost < 1 { + scanNode.Stats.Cost = 1 } - node.Stats.BlockNum = int32(node.Stats.Outcnt/2) + 1 + scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/2) + 1 + scanNode.Stats.Selectivity = andSelectivity(scanNode.Stats.Selectivity, runtimeFilterSel) } func calcScanStats(node *plan.Node, builder *QueryBuilder) *plan.Stats { From 77dd69a7cd48384c9f5e50b40ed03e8245349d60 Mon Sep 17 00:00:00 2001 From: nitao Date: Wed, 5 Jun 2024 21:39:43 +0800 Subject: [PATCH 3/5] fix stats for prefix_eq function (#16666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于索引表总是将主键序列化进去,导致ndv很高,索引表的过滤度估计严重错误,会导致优化器错判tp/ap语句 现在改成利用原始过滤条件的过滤度去计算prefix_eq函数的过滤度 Approved by: @aunjgr --- pkg/sql/plan/apply_indices.go | 16 ++++++++-- pkg/sql/plan/stats.go | 59 ++++++++++++++++++++--------------- pkg/sql/plan/utils.go | 1 + 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/pkg/sql/plan/apply_indices.go b/pkg/sql/plan/apply_indices.go index 37157fb596afb..da1d82c694d1d 100644 --- a/pkg/sql/plan/apply_indices.go +++ b/pkg/sql/plan/apply_indices.go @@ -514,9 +514,13 @@ func (builder *QueryBuilder) applyIndicesForFiltersRegularIndex(nodeID int32, no idxColMap[[2]int32{node.BindingTags[0], colIdx}] = mappedExpr } + compositeFilterSel := 1.0 serialArgs := make([]*plan.Expr, len(hitFilterIdx)) for i := range hitFilterIdx { - serialArgs[i] = DeepCopyExpr(node.FilterList[hitFilterIdx[i]].GetF().Args[1]) + filter := node.FilterList[hitFilterIdx[i]] + serialArgs[i] = DeepCopyExpr(filter.GetF().Args[1]) + estimateExprSelectivity(filter, builder) + compositeFilterSel = compositeFilterSel * filter.Selectivity } rightArg, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "serial", serialArgs) @@ -536,6 +540,7 @@ func (builder *QueryBuilder) applyIndicesForFiltersRegularIndex(nodeID int32, no }, rightArg, }) + idxFilter.Selectivity = compositeFilterSel newFilterList := make([]*plan.Expr, 0, len(missFilterIdx)+1) for _, idx := range missFilterIdx { @@ -658,9 +663,15 @@ END0: col.RelPos = idxTag col.ColPos = 0 } else { + + compositeFilterSel := 1.0 + serialArgs := make([]*plan.Expr, len(filterIdx)) for i := range filterIdx { - serialArgs[i] = DeepCopyExpr(node.FilterList[filterIdx[i]].GetF().Args[1]) + filter := node.FilterList[filterIdx[i]] + serialArgs[i] = DeepCopyExpr(filter.GetF().Args[1]) + estimateExprSelectivity(filter, builder) + compositeFilterSel = compositeFilterSel * filter.Selectivity } rightArg, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "serial", serialArgs) @@ -680,6 +691,7 @@ END0: }, rightArg, }) + idxFilter.Selectivity = compositeFilterSel } idxTableNodeID := builder.appendNode(&plan.Node{ diff --git a/pkg/sql/plan/stats.go b/pkg/sql/plan/stats.go index a787013bdf551..6a58d0eca9a6b 100644 --- a/pkg/sql/plan/stats.go +++ b/pkg/sql/plan/stats.go @@ -470,47 +470,46 @@ func estimateNonEqualitySelectivity(expr *plan.Expr, funcName string, builder *Q func estimateExprSelectivity(expr *plan.Expr, builder *QueryBuilder) float64 { if expr == nil { - return 1 + return 1.0 + } + if expr.Selectivity != 0 { + return expr.Selectivity // already calculated } + ret := 1.0 switch exprImpl := expr.Expr.(type) { case *plan.Expr_F: funcName := exprImpl.F.Func.ObjName switch funcName { case "=": - return estimateEqualitySelectivity(expr, builder) + ret = estimateEqualitySelectivity(expr, builder) case "!=", "<>": - return 0.9 + ret = 0.9 case ">", "<", ">=", "<=", "between": - return estimateNonEqualitySelectivity(expr, funcName, builder) + ret = estimateNonEqualitySelectivity(expr, funcName, builder) case "and": sel1 := estimateExprSelectivity(exprImpl.F.Args[0], builder) sel2 := estimateExprSelectivity(exprImpl.F.Args[1], builder) if canMergeToBetweenAnd(exprImpl.F.Args[0], exprImpl.F.Args[1]) && (sel1+sel2) > 1 { - return sel1 + sel2 - 1 + ret = sel1 + sel2 - 1 } else { - return andSelectivity(sel1, sel2) + ret = andSelectivity(sel1, sel2) } case "or": sel1 := estimateExprSelectivity(exprImpl.F.Args[0], builder) sel2 := estimateExprSelectivity(exprImpl.F.Args[1], builder) - return orSelectivity(sel1, sel2) + ret = orSelectivity(sel1, sel2) case "not": - return 1 - estimateExprSelectivity(exprImpl.F.Args[0], builder) + ret = 1 - estimateExprSelectivity(exprImpl.F.Args[0], builder) case "like": - return 0.2 + ret = 0.2 case "prefix_eq": - ndv := getExprNdv(expr, builder) - if ndv > 10 { - return 10 / ndv - } - return 0.5 + ret = 0.0001 // should never go here case "in", "prefix_in": - card := float64(1) + card := 1.0 switch arg1Impl := exprImpl.F.Args[1].Expr.(type) { case *plan.Expr_Vec: card = float64(arg1Impl.Vec.Len) - case *plan.Expr_List: card = float64(len(arg1Impl.List.List)) } @@ -519,22 +518,24 @@ func estimateExprSelectivity(expr *plan.Expr, builder *QueryBuilder) float64 { } ndv := getExprNdv(expr, builder) if ndv > card { - return card / ndv + ret = card / ndv + } else { + ret = 0.5 } - return 0.5 case "prefix_between": - return 0.1 + ret = 0.1 case "isnull", "is_null": - return getNullSelectivity(exprImpl.F.Args[0], builder, true) + ret = getNullSelectivity(exprImpl.F.Args[0], builder, true) case "isnotnull", "is_not_null": - return getNullSelectivity(exprImpl.F.Args[0], builder, false) + ret = getNullSelectivity(exprImpl.F.Args[0], builder, false) default: - return 0.15 + ret = 0.15 } case *plan.Expr_Lit: - return 1 + ret = 1.0 } - return 1 + expr.Selectivity = ret + return ret } func estimateFilterWeight(expr *plan.Expr, w float64) float64 { @@ -1000,13 +1001,21 @@ func recalcStatsByRuntimeFilter(scanNode *plan.Node, joinNode *plan.Node, builde if joinNode.NodeType != plan.Node_JOIN && joinNode.NodeType != plan.Node_FUZZY_FILTER { return } + + if joinNode.JoinType == plan.Node_INDEX { + scanNode.Stats.Outcnt = builder.qry.Nodes[joinNode.Children[1]].Stats.Outcnt + scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/3) + 1 + scanNode.Stats.Cost = float64(scanNode.Stats.BlockNum * DefaultBlockMaxRows) + scanNode.Stats.Selectivity = builder.qry.Nodes[joinNode.Children[1]].Stats.Selectivity + return + } runtimeFilterSel := builder.qry.Nodes[joinNode.Children[1]].Stats.Selectivity scanNode.Stats.Cost *= runtimeFilterSel scanNode.Stats.Outcnt *= runtimeFilterSel if scanNode.Stats.Cost < 1 { scanNode.Stats.Cost = 1 } - scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/2) + 1 + scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/3) + 1 scanNode.Stats.Selectivity = andSelectivity(scanNode.Stats.Selectivity, runtimeFilterSel) } diff --git a/pkg/sql/plan/utils.go b/pkg/sql/plan/utils.go index 5f74eeb0c88f6..f7051db1ebf8c 100644 --- a/pkg/sql/plan/utils.go +++ b/pkg/sql/plan/utils.go @@ -1798,6 +1798,7 @@ func doFormatExpr(expr *plan.Expr, out *bytes.Buffer, depth int) { default: out.WriteString(fmt.Sprintf("%sExpr_Unknown(%s)", prefix, expr.String())) } + out.WriteString(fmt.Sprintf("%sExpr_Selectivity(%v)", prefix, expr.Selectivity)) } // databaseIsValid checks whether the database exists or not. From faf96da3f61434c44fd10a7c458123b26653b4ae Mon Sep 17 00:00:00 2001 From: badboynt1 Date: Fri, 21 Jun 2024 09:32:26 +0800 Subject: [PATCH 4/5] fix --- pkg/sql/plan/build_constraint_util.go | 5 +---- pkg/sql/plan/stats.go | 8 ++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/sql/plan/build_constraint_util.go b/pkg/sql/plan/build_constraint_util.go index 0d85ea5610935..fa29a67c56aae 100644 --- a/pkg/sql/plan/build_constraint_util.go +++ b/pkg/sql/plan/build_constraint_util.go @@ -1370,10 +1370,7 @@ func appendPrimaryConstraintPlan( scanNode.RuntimeFilterProbeList = nil // can not use both } else { tableScanId = builder.appendNode(scanNode, bindCtx) - // temporary solution for the plan of dml go without optimizer - // prevent table scan from running on multiple CNs. - // because the runtime filter can only run on one now. - scanNode.Stats = DefaultMinimalStats() + scanNode.Stats.ForceOneCN = true } // Perform partition pruning on the full table scan of the partitioned table in the insert statement diff --git a/pkg/sql/plan/stats.go b/pkg/sql/plan/stats.go index 6a58d0eca9a6b..202b4df84b93a 100644 --- a/pkg/sql/plan/stats.go +++ b/pkg/sql/plan/stats.go @@ -1002,11 +1002,15 @@ func recalcStatsByRuntimeFilter(scanNode *plan.Node, joinNode *plan.Node, builde return } - if joinNode.JoinType == plan.Node_INDEX { + if joinNode.JoinType == plan.Node_INDEX || joinNode.NodeType == plan.Node_FUZZY_FILTER { scanNode.Stats.Outcnt = builder.qry.Nodes[joinNode.Children[1]].Stats.Outcnt scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/3) + 1 scanNode.Stats.Cost = float64(scanNode.Stats.BlockNum * DefaultBlockMaxRows) - scanNode.Stats.Selectivity = builder.qry.Nodes[joinNode.Children[1]].Stats.Selectivity + if scanNode.Stats.Cost > scanNode.Stats.TableCnt { + scanNode.Stats.Cost = scanNode.Stats.TableCnt + scanNode.Stats.BlockNum = int32(scanNode.Stats.TableCnt / DefaultBlockMaxRows) + } + scanNode.Stats.Selectivity = scanNode.Stats.Outcnt / scanNode.Stats.TableCnt return } runtimeFilterSel := builder.qry.Nodes[joinNode.Children[1]].Stats.Selectivity From 1867e513381c12b56828db96f70c1c4b414074ee Mon Sep 17 00:00:00 2001 From: badboynt1 Date: Fri, 21 Jun 2024 12:08:46 +0800 Subject: [PATCH 5/5] fix a bug --- pkg/sql/plan/stats.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/sql/plan/stats.go b/pkg/sql/plan/stats.go index 202b4df84b93a..2a81cf209d133 100644 --- a/pkg/sql/plan/stats.go +++ b/pkg/sql/plan/stats.go @@ -1004,8 +1004,11 @@ func recalcStatsByRuntimeFilter(scanNode *plan.Node, joinNode *plan.Node, builde if joinNode.JoinType == plan.Node_INDEX || joinNode.NodeType == plan.Node_FUZZY_FILTER { scanNode.Stats.Outcnt = builder.qry.Nodes[joinNode.Children[1]].Stats.Outcnt + if scanNode.Stats.Outcnt > scanNode.Stats.TableCnt { + scanNode.Stats.Outcnt = scanNode.Stats.TableCnt + } scanNode.Stats.BlockNum = int32(scanNode.Stats.Outcnt/3) + 1 - scanNode.Stats.Cost = float64(scanNode.Stats.BlockNum * DefaultBlockMaxRows) + scanNode.Stats.Cost = float64(scanNode.Stats.BlockNum) * DefaultBlockMaxRows if scanNode.Stats.Cost > scanNode.Stats.TableCnt { scanNode.Stats.Cost = scanNode.Stats.TableCnt scanNode.Stats.BlockNum = int32(scanNode.Stats.TableCnt / DefaultBlockMaxRows)