From 440eb07e18e98bd9bee44df90f4872ac82d4f1aa Mon Sep 17 00:00:00 2001 From: ep-12221 Date: Thu, 11 Jun 2026 18:23:35 +0800 Subject: [PATCH 1/2] fix(optimizer): handle NULL opt_stat_manager on Windows with default stats Two-part fix for Windows where opt_stat_manager is unavailable 1. In ObJoinOrder::get_used_stat_partitions: When opt_stat_manager is NULL on Windows, set get_stat=true to skip stat retrieval and return success instead of OB_ERR_UNEXPECTED. This prevents the caller from aborting before add_base_table_meta_info populates basic_table_metas. 2. In OptTableMeta::init_column_meta: When opt_stat_manager or session_info is NULL on Windows, populate column metadata with defaults via set_default_meta instead of returning OB_ERR_UNEXPECTED. Root cause: On Windows, NULL opt_stat_manager caused an error cascade get_used_stat_partitions returns error -> basic_table_metas stays empty -> get_column_basic_from_meta can't find column metadata -> falls through to get_var_basic_default which sets ndv=1.0 -> join selectivity ~0.98 -> EST.ROWS=9 (3*3*0.98) instead of correct value. DIMA: 2026042800115792720 Co-Authored-By: Claude Opus 4.6 --- src/sql/optimizer/ob_join_order.cpp | 5 +++++ src/sql/optimizer/ob_opt_selectivity.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/sql/optimizer/ob_join_order.cpp b/src/sql/optimizer/ob_join_order.cpp index b8600eb9d..0b9593aa0 100644 --- a/src/sql/optimizer/ob_join_order.cpp +++ b/src/sql/optimizer/ob_join_order.cpp @@ -15868,8 +15868,13 @@ int ObJoinOrder::get_used_stat_partitions(const uint64_t ref_table_id, OB_ISNULL(session_info = OPT_CTX.get_session_info()) || OB_ISNULL(OPT_CTX.get_exec_ctx()) || OB_ISNULL(opt_stat_manager = OPT_CTX.get_opt_stat_manager())) { + #ifdef _WIN32 + LOG_WARN("opt stat manager is null on Windows, fall back to default stats"); + get_stat = true; + #else ret = OB_ERR_UNEXPECTED; LOG_WARN("get unexpected null", K(ret)); + #endif } else if (OPT_CTX.use_default_stat()) { get_stat = true; } else if (OB_FAIL(get_partition_infos(ref_table_id, schema, all_used_part_ids, diff --git a/src/sql/optimizer/ob_opt_selectivity.cpp b/src/sql/optimizer/ob_opt_selectivity.cpp index 5f29ad362..8fb611731 100644 --- a/src/sql/optimizer/ob_opt_selectivity.cpp +++ b/src/sql/optimizer/ob_opt_selectivity.cpp @@ -442,8 +442,20 @@ int OptTableMeta::init_column_meta(const OptSelectivityCtx &ctx, } else if (!column_ids.empty()) { // batch get column stats if ((OB_ISNULL(ctx.get_opt_stat_manager()) || OB_ISNULL(ctx.get_session_info()))) { + #ifdef _WIN32 + LOG_WARN("opt stat manager or session info is null, fall back to default", K(ret), KPC(this)); + ret = OB_SUCCESS; + for (int64_t i = 0; OB_SUCC(ret) && i < column_ids.count(); ++i) { + ObGlobalColumnStat s; + column_metas.at(i).set_default_meta(rows_); + if (OB_FAIL(col_stats.push_back(s))) { + LOG_WARN("failed to push back column id", K(ret)); + } + } +#else ret = OB_ERR_UNEXPECTED; LOG_WARN("get unexpected null", K(ret), K(ctx.get_opt_stat_manager()), K(ctx.get_session_info())); +#endif } else if (OB_FAIL(ctx.get_opt_stat_manager()->batch_get_column_stats( ctx.get_session_info()->get_effective_tenant_id(), ref_table_id_, From 751b9b15208fe98f9296e5eb1b14911ea9b3824e Mon Sep 17 00:00:00 2001 From: ep-12221 Date: Fri, 12 Jun 2026 12:17:44 +0800 Subject: [PATCH 2/2] Fix: prefer highest-row-count entry in get_table_meta_by_table_id When update_table_meta_info copies table meta entries from basic_table_metas_ to update_table_metas_ via copy_table_meta_info it uses push_back which can create multiple entries for the same table_id with different row counts (e.g. rows=1 from default stats rows=3 from storage estimation). The original get_table_meta_by_table_id returned only the first matching entry, which could be a stale entry with rows_=1 leading to ndv=1, join selectivity=1.0, and inflated EXPLAIN EST.ROWS. Fix by iterating all entries and returning the one with the highest row count, ensuring join selectivity uses the most accurate estimate. Fixes DIMA bug: 2026042800115792720 (EXPLAIN JOIN EST.ROWS=9 instead of 3 on multi-part PK tables) Co-Authored-By: Claude Opus 4.6 --- src/sql/optimizer/ob_opt_selectivity.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/sql/optimizer/ob_opt_selectivity.cpp b/src/sql/optimizer/ob_opt_selectivity.cpp index 8fb611731..78febe75f 100644 --- a/src/sql/optimizer/ob_opt_selectivity.cpp +++ b/src/sql/optimizer/ob_opt_selectivity.cpp @@ -507,7 +507,7 @@ int OptTableMeta::refine_column_meta(const OptSelectivityCtx &ctx, const uint64_t column_id, const ObGlobalColumnStat &stat, OptColumnMeta &col_meta) -{ +{ int ret = OB_SUCCESS; bool is_single_pkey = (1 == pk_ids_.count() && pk_ids_.at(0) == column_id) || column_id == OB_HIDDEN_PK_INCREMENT_COLUMN_ID; @@ -1031,9 +1031,11 @@ int OptTableMetas::get_set_stmt_output_ndv(const ObSelectStmt &stmt, const OptTableMeta* OptTableMetas::get_table_meta_by_table_id(const uint64_t table_id) const { const OptTableMeta *table_meta = NULL; - for (int64_t i = 0; NULL == table_meta && i < table_metas_.count(); ++i) { + for (int64_t i = 0; i < table_metas_.count(); ++i) { if (table_id == table_metas_.at(i).get_table_id()) { - table_meta = &table_metas_.at(i); + if (NULL == table_meta || table_metas_.at(i).get_rows() > table_meta->get_rows()) { + table_meta = &table_metas_.at(i); + } } } return table_meta; @@ -1042,9 +1044,11 @@ const OptTableMeta* OptTableMetas::get_table_meta_by_table_id(const uint64_t tab OptTableMeta* OptTableMetas::get_table_meta_by_table_id(const uint64_t table_id) { OptTableMeta *table_meta = NULL; - for (int64_t i = 0; NULL == table_meta && i < table_metas_.count(); ++i) { + for (int64_t i = 0; i < table_metas_.count(); ++i) { if (table_id == table_metas_.at(i).get_table_id()) { - table_meta = &table_metas_.at(i); + if (NULL == table_meta || table_metas_.at(i).get_rows() > table_meta->get_rows()) { + table_meta = &table_metas_.at(i); + } } } return table_meta;