From 2bbef5c10c7c0a8761bfd5e834bc10cba6529a0c Mon Sep 17 00:00:00 2001 From: bsrikanth-mariadb Date: Fri, 29 May 2026 12:29:58 +0530 Subject: [PATCH] MDEV-39791: Handle count aggregate optimization for replay purpose Separately dump into the optimizer context, the count aggregation value of any one non-nullable column of a table if any are used in the query. This is only suppoerted for myisam tables. During opt_sum_query() call, record the count aggregation values into the optimizer context recorder. Once the context is being stored into the information_schema table, dump all these recorded values into it. During replay, parse the count aggregate values and store them in memory. Later when opt_sum_query() requires the count value, hook the recorded value from the parsed context. --- .../get_rec_idx_ranges_from_opt_ctx.inc | 6 + mysql-test/include/opt_context_schema.inc | 3 + .../main/opt_context_load_stats_basic.result | 46 ++++- .../main/opt_context_load_stats_basic.test | 28 ++- .../main/opt_context_replay_basic.result | 4 +- .../main/opt_context_store_stats.result | 190 ++++++++++++++++++ mysql-test/main/opt_context_store_stats.test | 20 ++ sql/opt_context_store_replay.cc | 85 ++++++-- sql/opt_context_store_replay.h | 3 +- sql/opt_sum.cc | 22 +- 10 files changed, 379 insertions(+), 28 deletions(-) diff --git a/mysql-test/include/get_rec_idx_ranges_from_opt_ctx.inc b/mysql-test/include/get_rec_idx_ranges_from_opt_ctx.inc index 7e0a363c8af34..a1ef86fb818fe 100644 --- a/mysql-test/include/get_rec_idx_ranges_from_opt_ctx.inc +++ b/mysql-test/include/get_rec_idx_ranges_from_opt_ctx.inc @@ -25,3 +25,9 @@ select *from json_table( num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; +set @table_contexts= + (select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( + @table_contexts, + '$[*][*]' columns(table_name text path '$.name', + count_agg int path '$.count_agg')) as jt; diff --git a/mysql-test/include/opt_context_schema.inc b/mysql-test/include/opt_context_schema.inc index 855f988eb5aa4..d86d1d01004fe 100644 --- a/mysql-test/include/opt_context_schema.inc +++ b/mysql-test/include/opt_context_schema.inc @@ -187,6 +187,9 @@ let $opt_context_schema= "copy_cost" ] } + }, + "count_agg": { + "type": "number" } }, "required": [ diff --git a/mysql-test/main/opt_context_load_stats_basic.result b/mysql-test/main/opt_context_load_stats_basic.result index 054dc827ed7aa..9ca19dd9fdfb3 100644 --- a/mysql-test/main/opt_context_load_stats_basic.result +++ b/mysql-test/main/opt_context_load_stats_basic.result @@ -68,6 +68,14 @@ index_name ranges num_rows max_index_blocks max_row_blocks t1_idx_a ["(NULL) < (a) < (3)"] 12 1 1 t1_idx_b ["(6) < (b)"] 2 1 1 t1_idx_ab ["(NULL) < (a) < (3)"] 12 1 1 +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL set @saved_opt_context_1=@opt_context; set @saved_records_1=@records; set @saved_indexes_1=@indexes; @@ -355,11 +363,6 @@ select * from t1 where a > 10; a b Warnings: Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1380. -set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat_records'); -select * from t1 where a > 10; -a b -Warnings: -Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1380. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0].index_name'); select * from t1 where a > 10; a b @@ -488,4 +491,37 @@ a b Warnings: Warning 4253 Failed to parse saved optimizer context: "copy_cost" element not present at offset 514. drop table t1; +set optimizer_replay_context=""; +create table t1 (a int primary key, b int not null, c varchar(10)); +insert into t1 select seq, seq%5, concat('a-', seq) from seq_1_to_10; +set optimizer_record_context=1; +explain +select count(a), count(b), count(c) from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 +set @opt_context= +(select REGEXP_SUBSTR( +context, +'(?<=set @opt_context=\')([\n\r].*)*(?=\'\;#opt_context_ends)') +from information_schema.optimizer_context); +set @saved_opt_context_1= @opt_context; +set optimizer_replay_context=@opt_context_var_name; +set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].count_agg'); +explain +select count(a) from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +Warnings: +Warning 4254 Failed to match the stats from replay context with the optimizer stats: db1.t1 with count_agg: doesn't exist in list_contexts +explain +select count(b) from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +Warnings: +Warning 4254 Failed to match the stats from replay context with the optimizer stats: db1.t1 with count_agg: doesn't exist in list_contexts +explain +select count(c) from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 +drop table t1; drop database db1; diff --git a/mysql-test/main/opt_context_load_stats_basic.test b/mysql-test/main/opt_context_load_stats_basic.test index 323648fe625cd..d7ea435c2a385 100644 --- a/mysql-test/main/opt_context_load_stats_basic.test +++ b/mysql-test/main/opt_context_load_stats_basic.test @@ -226,9 +226,6 @@ select * from t1 where a > 10; set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat_records'); select * from t1 where a > 10; -set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat_records'); -select * from t1 where a > 10; - set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0].index_name'); select * from t1 where a > 10; @@ -300,5 +297,30 @@ select * from t1 where a > 10; set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_index_read_costs[0].copy_cost'); select * from t1 where a > 10; +drop table t1; +set optimizer_replay_context=""; + +create table t1 (a int primary key, b int not null, c varchar(10)); +insert into t1 select seq, seq%5, concat('a-', seq) from seq_1_to_10; + +set optimizer_record_context=1; + +explain +select count(a), count(b), count(c) from t1; + +--source include/get_opt_context.inc +set @saved_opt_context_1= @opt_context; + +set optimizer_replay_context=@opt_context_var_name; +set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].count_agg'); +explain +select count(a) from t1; + +explain +select count(b) from t1; + +explain +select count(c) from t1; + drop table t1; drop database db1; diff --git a/mysql-test/main/opt_context_replay_basic.result b/mysql-test/main/opt_context_replay_basic.result index 8d5c1fab4058b..baf634bef844f 100644 --- a/mysql-test/main/opt_context_replay_basic.result +++ b/mysql-test/main/opt_context_replay_basic.result @@ -124,7 +124,7 @@ SET optimizer_where_cost=0.032000; SET sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; -SET timestamp=1780212030.000000; +SET timestamp=1780203030.000000; select count(*) from t1; @@ -205,7 +205,7 @@ SET optimizer_trace_max_mem_size=1048576; SET optimizer_use_condition_selectivity=4; SET optimizer_where_cost=0.032000; SET sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; -SET timestamp=1780212030.000000; +SET timestamp=1780203030.000000; CREATE DATABASE IF NOT EXISTS db1; Warnings: Note 1007 Can't create database 'db1'; database exists diff --git a/mysql-test/main/opt_context_store_stats.result b/mysql-test/main/opt_context_store_stats.result index 832e0b2bbcdb9..35502995e836f 100644 --- a/mysql-test/main/opt_context_store_stats.result +++ b/mysql-test/main/opt_context_store_stats.result @@ -73,6 +73,14 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL # # simple query using join of two tables # @@ -121,6 +129,15 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t2 NULL +db1.t1 NULL # # negative test # simple query using join of two tables @@ -161,6 +178,13 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg set optimizer_record_context=ON; # # there should be no duplicate information @@ -215,6 +239,15 @@ t1_idx_a ["(5) <= (a) <= (5)"] 1 1 1 t1_idx_ab ["(5) <= (a) <= (5)"] 1 1 1 t1_idx_a ["(5) <= (a) <= (5)"] 1 1 1 t1_idx_ab ["(5) <= (a) <= (5)"] 1 1 1 +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t2 NULL +db1.t1 NULL # # test for update # @@ -258,6 +291,14 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL # # test for insert as select # @@ -304,6 +345,15 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t2 NULL +db1.t1 NULL analyze table t1 persistent for all; Table Op Msg_type Msg_text db1.t1 analyze status Engine-independent statistics collected @@ -359,6 +409,14 @@ index_name ranges num_rows max_index_blocks max_row_blocks t1_idx_a ["(1) <= (a) <= (5)"] 35 1 1 t1_idx_ab ["(1) <= (a) <= (5)"] 35 1 1 t1_idx_b ["(6) <= (b) <= (10)"] 1 1 1 +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL # # simple query with or condition on the same column # @@ -412,6 +470,14 @@ t1_idx_ab [ "(1) <= (a) <= (5)", "(6) <= (a) <= (10)" ] 36 2 1 +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL # # negative test on the simple query with or condition on 2 columns # @@ -450,6 +516,13 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg set optimizer_record_context=ON; # # simple query with or condition on 2 columns @@ -500,6 +573,14 @@ index_name ranges num_rows max_index_blocks max_row_blocks t1_idx_a ["(1) <= (a) <= (5)"] 35 1 1 t1_idx_ab ["(1) <= (a) <= (5)"] 35 1 1 t1_idx_b ["(6) <= (b) <= (10)"] 1 1 1 +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +db1.t1 NULL drop view view1; drop table t1; drop table t2; @@ -900,6 +981,115 @@ num_rows int path '$.num_rows', max_index_blocks int path '$.max_index_blocks', max_row_blocks int path '$.max_row_blocks')) as jt; index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +test.t1 NULL drop function add1; DROP TABLE t1; set session use_stat_tables=@saved_use_stat_tables; +# +# MDEV-39791: Handle count aggregate optimization for replay purpose +# +create table t1 (a int primary key, b int not null, c varchar(10)); +insert into t1 select seq, seq%5, concat('a-', seq) from seq_1_to_10; +set optimizer_record_context=1; +explain +select count(a), count(b), count(c) from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 +set @opt_context= +(select REGEXP_SUBSTR( +context, +'(?<=set @opt_context=\')([\n\r].*)*(?=\'\;#opt_context_ends)') +from information_schema.optimizer_context); +set @records= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.file_stat_records'))); +select *from json_table(@records, +'$[*]' columns(file_stat_records text path '$')) as jt; +file_stat_records +10 +set @file_stat_records= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.file_stat_records'))); +select *from json_table(@file_stat_records, +'$[*]' columns(file_stat_records text path '$')) as jt; +file_stat_records +10 +set @indexes= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.indexes'))); +select *from json_table( +@indexes, '$[*][*]' columns(index_name text path '$.index_name', +rec_per_key json path '$.rec_per_key')) as jt; +index_name rec_per_key +PRIMARY ["1"] +set @list_ranges= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_ranges'))); +select *from json_table( +@list_ranges, +'$[*][*]' columns(index_name text path '$.index_name', +ranges json path '$.ranges', +num_rows int path '$.num_rows', +max_index_blocks int path '$.max_index_blocks', +max_row_blocks int path '$.max_row_blocks')) as jt; +index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +test.t1 10 +explain +select count(c) from t1 where b = (select count(b) from t1) or a = (select count(a) from t1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL PRIMARY NULL NULL NULL 10 Using where +3 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +set @opt_context= +(select REGEXP_SUBSTR( +context, +'(?<=set @opt_context=\')([\n\r].*)*(?=\'\;#opt_context_ends)') +from information_schema.optimizer_context); +set @records= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.file_stat_records'))); +select *from json_table(@records, +'$[*]' columns(file_stat_records text path '$')) as jt; +file_stat_records +10 +set @file_stat_records= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.file_stat_records'))); +select *from json_table(@file_stat_records, +'$[*]' columns(file_stat_records text path '$')) as jt; +file_stat_records +10 +set @indexes= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.indexes'))); +select *from json_table( +@indexes, '$[*][*]' columns(index_name text path '$.index_name', +rec_per_key json path '$.rec_per_key')) as jt; +index_name rec_per_key +PRIMARY ["1"] +set @list_ranges= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_ranges'))); +select *from json_table( +@list_ranges, +'$[*][*]' columns(index_name text path '$.index_name', +ranges json path '$.ranges', +num_rows int path '$.num_rows', +max_index_blocks int path '$.max_index_blocks', +max_row_blocks int path '$.max_row_blocks')) as jt; +index_name ranges num_rows max_index_blocks max_row_blocks +set @table_contexts= +(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.list_contexts'))); +select *from json_table( +@table_contexts, +'$[*][*]' columns(table_name text path '$.name', +count_agg int path '$.count_agg')) as jt; +table_name count_agg +test.t1 10 +drop table t1; diff --git a/mysql-test/main/opt_context_store_stats.test b/mysql-test/main/opt_context_store_stats.test index e71279c3db95d..fa2d8631f4673 100644 --- a/mysql-test/main/opt_context_store_stats.test +++ b/mysql-test/main/opt_context_store_stats.test @@ -421,3 +421,23 @@ explain select * from t1 where b < add1(3); drop function add1; DROP TABLE t1; set session use_stat_tables=@saved_use_stat_tables; + +--echo # +--echo # MDEV-39791: Handle count aggregate optimization for replay purpose +--echo # +create table t1 (a int primary key, b int not null, c varchar(10)); +insert into t1 select seq, seq%5, concat('a-', seq) from seq_1_to_10; + +set optimizer_record_context=1; + +explain +select count(a), count(b), count(c) from t1; + +--source include/get_rec_idx_ranges_from_opt_ctx.inc + +explain +select count(c) from t1 where b = (select count(b) from t1) or a = (select count(a) from t1); + +--source include/get_rec_idx_ranges_from_opt_ctx.inc + +drop table t1; diff --git a/sql/opt_context_store_replay.cc b/sql/opt_context_store_replay.cc index f17edf3483c83..470192cdde99e 100644 --- a/sql/opt_context_store_replay.cc +++ b/sql/opt_context_store_replay.cc @@ -73,7 +73,8 @@ using namespace json_reader; { ... }, ..., - ] + ], + count_agg: n //optional }, ... ] } @@ -152,6 +153,7 @@ class table_context_for_store : public Sql_alloc List irc_list; List rir_list; List const_tbl_ins_stmt_list; + longlong count_agg= -1; }; namespace Show @@ -191,6 +193,8 @@ static bool parse_range_cost_estimate(MEM_ROOT*, json_engine_t *je, static int parse_records_in_range_context(MEM_ROOT *mem_root, json_engine_t *je, String *err_buf, records_in_range_call_record *rir_ctx); +// static int parse_count_agg_context(MEM_ROOT *mem_root, json_engine_t *je, +// String *err_buf, count_agg *out); static char *strdup_root(MEM_ROOT *root, const String *str) { @@ -308,16 +312,10 @@ void dump_records_in_range_calls(List *rir_list, } } -static -void dump_recorded_table_calls(THD *thd, uchar *tbl_name, size_t tbl_name_len, - Json_writer *ctx_writer) +static void dump_recorded_table_calls(THD *thd, + table_context_for_store *table_context, + Json_writer *ctx_writer) { - table_context_for_store *table_context= - thd->opt_ctx_recorder->search(tbl_name, tbl_name_len); - - if (!table_context) - return; - dump_mrr_info_calls(&table_context->mrr_list, ctx_writer); dump_index_read_calls(&table_context->irc_list, ctx_writer); dump_records_in_range_calls(&table_context->rir_list, ctx_writer); @@ -359,7 +357,14 @@ static void dump_table_stats(THD *thd, TABLE_LIST *tbl, uchar *tbl_name, rpk_wrapper.end(); } indexes_wrapper.end(); - dump_recorded_table_calls(thd, tbl_name, tbl_name_len, ctx_writer); + table_context_for_store *table_context= + thd->opt_ctx_recorder->search(tbl_name, tbl_name_len); + if (table_context) + { + dump_recorded_table_calls(thd, table_context, ctx_writer); + if (table_context->count_agg != -1) + ctx_wrapper.add("count_agg", table_context->count_agg); + } } static void create_view_def(THD *thd, TABLE_LIST *table, String *name, @@ -840,6 +845,22 @@ Optimizer_context_recorder::~Optimizer_context_recorder() my_hash_free(&tbl_ctx_hash); } +void Optimizer_context_recorder::record_count_agg(TABLE *tbl, longlong count) +{ + String tbl_name; + append_full_table_name(tbl->pos_in_table_list, &tbl_name); + if (search((uchar *) tbl_name.c_ptr_safe(), tbl_name.length())) + return; // dupicate entry + + table_context_for_store *table_ctx= + get_table_context(tbl->pos_in_table_list); + + if (unlikely(!table_ctx)) + return; // OOM + + table_ctx->count_agg= count; +} + bool Optimizer_context_recorder::has_records() { return tbl_ctx_hash.records > 0; @@ -1050,6 +1071,7 @@ class table_context_for_replay : public Sql_alloc List ranges_list; List irc_list; List rir_list; + longlong count_agg= -1; }; /* @@ -1257,7 +1279,8 @@ static int parse_table_context(MEM_ROOT *mem_root, json_engine_t *je, Read_named_member array[]= { {"name", Read_string(mem_root, &table_ctx->name), false}, {"file_stat_records", - Read_non_neg_integer(&table_ctx->file_stat_records), + Read_non_neg_integer( + &table_ctx->file_stat_records), false}, {"read_cost_io", Read_double(&table_ctx->read_cost_io), false}, {"read_cost_cpu", Read_double(&table_ctx->read_cost_cpu), false}, @@ -1277,6 +1300,9 @@ static int parse_table_context(MEM_ROOT *mem_root, json_engine_t *je, Read_array_into_list( mem_root, &table_ctx->rir_list, parse_records_in_range_context), true}, + {"count_agg", + Read_non_neg_integer(&table_ctx->count_agg), + true}, {NULL, Read_double(NULL), true}}; return parse_context_obj_from_json_array(je, err_buf, err_msg, array); @@ -1677,10 +1703,10 @@ bool Optimizer_context_replay::infuse_index_read_cost(const TABLE *tbl, } } - String warn_msg; + String warn_msg(256); warn_msg.append(tbl_name); warn_msg.append(STRING_WITH_LEN(" with key_number:")); - warn_msg.append(keynr); + warn_msg.q_append(keynr); warn_msg.append(STRING_WITH_LEN(", records:")); warn_msg.q_append_int64(records); warn_msg.append(STRING_WITH_LEN(", eq_ref:")); @@ -1799,10 +1825,10 @@ bool Optimizer_context_replay::infuse_records_in_range( } } - String warn_msg; + String warn_msg(256); warn_msg.append(tbl_name); warn_msg.append(STRING_WITH_LEN(" with key_number:")); - warn_msg.append(keynr); + warn_msg.q_append(keynr); warn_msg.append(STRING_WITH_LEN(" with min_key:")); warn_msg.append(min_key); warn_msg.append(STRING_WITH_LEN(" with max_key:")); @@ -1815,6 +1841,33 @@ bool Optimizer_context_replay::infuse_records_in_range( return true; } +bool Optimizer_context_replay::infuse_count_agg(const TABLE *tbl, + longlong *count) +{ + if (!has_records() || !is_base_table(tbl->pos_in_table_list)) + return true; + + String tbl_name; + append_full_table_name(tbl->pos_in_table_list, &tbl_name); + + table_context_for_replay *tbl_ctx= find_table_context(tbl_name.c_ptr_safe()); + if (tbl_ctx && tbl_ctx->count_agg != -1) + { + *count= tbl_ctx->count_agg; + return false; + } + + String warn_msg(256); + warn_msg.append(tbl_name); + warn_msg.append(STRING_WITH_LEN(" with count_agg: ")); + push_warning_printf( + thd, Sql_condition::WARN_LEVEL_WARN, + ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED, + ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED), + warn_msg.c_ptr_safe(), "list_contexts"); + return true; +} + /* @brief restore the saved stats for the tables, and indexes that were diff --git a/sql/opt_context_store_replay.h b/sql/opt_context_store_replay.h index fe6667f27f85d..180c05b38692c 100644 --- a/sql/opt_context_store_replay.h +++ b/sql/opt_context_store_replay.h @@ -72,7 +72,7 @@ class Optimizer_context_recorder /* use table->record[0] */ record_table_row(tbl, 0); } - + void record_count_agg(TABLE *tbl, longlong count); bool has_records(); table_context_for_store *search(uchar *tbl_name, size_t tbl_name_len); @@ -146,6 +146,7 @@ class Optimizer_context_replay bool infuse_records_in_range(const TABLE *tbl, const KEY_PART_INFO *key_part, uint keynr, const key_range *min_range, const key_range *max_range, ha_rows *records); + bool infuse_count_agg(const TABLE *tbl, longlong *count); private: bool infuse_table_rows(const TABLE *tbl, ha_rows *rows); diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index 9ab9dcfaabe09..f920f0fd7ea58 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -374,8 +374,28 @@ int opt_sum_query(THD *thd, } is_exact_count= 1; // count is now exact } - ((Item_sum_count*) item)->make_const((longlong) count); + Item_sum_count *cnt_item= static_cast(item); + cnt_item->make_const((longlong) count); recalc_const_item= 1; + Item *expr= item_sum->get_arg(0); + if (expr && ((expr->used_tables() & OUTER_REF_TABLE_BIT) == 0) && + expr->real_item()->type() == Item::FIELD_ITEM) + { + Item_field *item_field= (Item_field *) (expr->real_item()); + TABLE *table= item_field->field->table; + if (Optimizer_context_replay *replay= thd->opt_ctx_replay) + { + longlong tmp_count; + if (!replay->infuse_count_agg(table, &tmp_count)) + { + count= tmp_count; + } + } + if (Optimizer_context_recorder *rec= thd->opt_ctx_recorder) + { + rec->record_count_agg(table, count); + } + } } else const_result= 0;