diff --git a/mysql-test/main/opt_context_load_stats_basic.result b/mysql-test/main/opt_context_load_stats_basic.result index 054dc827ed7aa..516e5f6199aa6 100644 --- a/mysql-test/main/opt_context_load_stats_basic.result +++ b/mysql-test/main/opt_context_load_stats_basic.result @@ -343,7 +343,7 @@ set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].name'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "name" element not present at offset 1387. +Warning 4253 Failed to parse saved optimizer context: "name" element not present at offset 1441. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].ddl'); select * from t1 where a > 10; a b @@ -354,12 +354,12 @@ set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat 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. +Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1434. 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. +Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1434. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0].index_name'); select * from t1 where a > 10; a b @@ -374,32 +374,37 @@ set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_rang select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "index_name" element not present at offset 621. +Warning 4253 Failed to parse saved optimizer context: "index_name" element not present at offset 639. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].ranges'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "ranges" element not present at offset 613. +Warning 4253 Failed to parse saved optimizer context: "ranges" element not present at offset 631. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].num_rows'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "num_rows" element not present at offset 631. +Warning 4253 Failed to parse saved optimizer context: "num_rows" element not present at offset 649. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].cost'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "cost" element not present at offset 415. +Warning 4253 Failed to parse saved optimizer context: "cost" element not present at offset 433. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].max_index_blocks'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "max_index_blocks" element not present at offset 624. +Warning 4253 Failed to parse saved optimizer context: "max_index_blocks" element not present at offset 642. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].max_row_blocks'); select * from t1 where a > 10; a b Warnings: -Warning 4253 Failed to parse saved optimizer context: "max_row_blocks" element not present at offset 626. +Warning 4253 Failed to parse saved optimizer context: "max_row_blocks" element not present at offset 644. +set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].call_number'); +select * from t1 where a > 10; +a b +Warnings: +Warning 4253 Failed to parse saved optimizer context: "call_number" element not present at offset 647. set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0]'); select * from t1 where a > 10; a b diff --git a/mysql-test/main/opt_context_load_stats_basic.test b/mysql-test/main/opt_context_load_stats_basic.test index 323648fe625cd..5a3e8f1c7108a 100644 --- a/mysql-test/main/opt_context_load_stats_basic.test +++ b/mysql-test/main/opt_context_load_stats_basic.test @@ -253,6 +253,9 @@ select * from t1 where a > 10; set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].max_row_blocks'); select * from t1 where a > 10; +set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].list_ranges[0].call_number'); +select * from t1 where a > 10; + set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0]'); select * from t1 where a > 10; diff --git a/mysql-test/main/opt_context_replay_basic.result b/mysql-test/main/opt_context_replay_basic.result index 6ffa4f5a55e37..101b02214915e 100644 --- a/mysql-test/main/opt_context_replay_basic.result +++ b/mysql-test/main/opt_context_replay_basic.result @@ -409,4 +409,130 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL btn NULL NULL NULL 4 Using where set optimizer_replay_context=''; drop table t1; +# +# MDEV-39538: Different cost when same rangeis read twice +# +create table t1 (pk int primary key, a datetime, c int, key(a)); +insert into t1 (pk,a,c) values (1,'2009-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (2,'2009-11-29 03:23:32', 2); +insert into t1 (pk,a,c) values (3,'2009-10-16 05:56:32', 2); +insert into t1 (pk,a,c) values (4,'2010-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (5,'2010-10-16 05:56:32', 2); +insert into t1 (pk,a,c) values (6,'2011-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (7,'2012-10-16 05:56:32', 2); +set optimizer_record_context=1; +explain format=json select * from t1 +where year(a) = 2010 and c < (select count(*) from t1 where year(a) = 2010); +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.003808422, + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["a"], + "key": "a", + "key_length": "6", + "used_key_parts": ["a"], + "loops": 1, + "rows": 2, + "cost": 0.003808422, + "filtered": 100, + "index_condition": "t1.a between '2010-01-01 00:00:00' and '2010-12-31 23:59:59'", + "attached_condition": "t1.c < (subquery#2)" + } + } + ], + "subqueries": [ + { + "query_block": { + "select_id": 2, + "cost": 0.001617224, + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["a"], + "key": "a", + "key_length": "6", + "used_key_parts": ["a"], + "loops": 1, + "rows": 2, + "cost": 0.001617224, + "filtered": 100, + "attached_condition": "t1.a between '2010-01-01 00:00:00' and '2010-12-31 23:59:59'", + "using_index": true + } + } + ] + } + } + ] + } +} +select context into dumpfile "../../tmp/dump1.sql" +from information_schema.optimizer_context; +set optimizer_record_context=0; +drop table t1; +set optimizer_replay_context='opt_context'; +# Same query as above, must have same explain cost: +explain format=json select * from t1 +where year(a) = 2010 and c < (select count(*) from t1 where year(a) = 2010); +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.003808422, + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["a"], + "key": "a", + "key_length": "6", + "used_key_parts": ["a"], + "loops": 1, + "rows": 2, + "cost": 0.003808422, + "filtered": 100, + "index_condition": "t1.a between '2010-01-01 00:00:00' and '2010-12-31 23:59:59'", + "attached_condition": "t1.c < (subquery#2)" + } + } + ], + "subqueries": [ + { + "query_block": { + "select_id": 2, + "cost": 0.001617224, + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["a"], + "key": "a", + "key_length": "6", + "used_key_parts": ["a"], + "loops": 1, + "rows": 2, + "cost": 0.001617224, + "filtered": 100, + "attached_condition": "t1.a between '2010-01-01 00:00:00' and '2010-12-31 23:59:59'", + "using_index": true + } + } + ] + } + } + ] + } +} +set optimizer_replay_context=''; +drop table t1; drop database db1; diff --git a/mysql-test/main/opt_context_replay_basic.test b/mysql-test/main/opt_context_replay_basic.test index 70b712ac825df..e2d13965f8681 100644 --- a/mysql-test/main/opt_context_replay_basic.test +++ b/mysql-test/main/opt_context_replay_basic.test @@ -200,4 +200,39 @@ set optimizer_replay_context=''; --remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql" drop table t1; +--echo # +--echo # MDEV-39538: Different cost when same rangeis read twice +--echo # +create table t1 (pk int primary key, a datetime, c int, key(a)); + +insert into t1 (pk,a,c) values (1,'2009-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (2,'2009-11-29 03:23:32', 2); +insert into t1 (pk,a,c) values (3,'2009-10-16 05:56:32', 2); +insert into t1 (pk,a,c) values (4,'2010-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (5,'2010-10-16 05:56:32', 2); +insert into t1 (pk,a,c) values (6,'2011-11-29 13:43:32', 2); +insert into t1 (pk,a,c) values (7,'2012-10-16 05:56:32', 2); + +set optimizer_record_context=1; +explain format=json select * from t1 + where year(a) = 2010 and c < (select count(*) from t1 where year(a) = 2010); +select context into dumpfile "../../tmp/dump1.sql" +from information_schema.optimizer_context; +set optimizer_record_context=0; +drop table t1; +--disable_query_log +--disable_result_log +--source "$MYSQLTEST_VARDIR/tmp/dump1.sql" +--enable_query_log +--enable_result_log +set optimizer_replay_context='opt_context'; +--echo # Same query as above, must have same explain cost: +explain format=json select * from t1 + where year(a) = 2010 and c < (select count(*) from t1 where year(a) = 2010); + +set optimizer_replay_context=''; +--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql" +drop table t1; + + drop database db1; diff --git a/sql/opt_context_store_replay.cc b/sql/opt_context_store_replay.cc index 7692b34d9a1e5..06c3f4f45324d 100644 --- a/sql/opt_context_store_replay.cc +++ b/sql/opt_context_store_replay.cc @@ -112,6 +112,7 @@ class Multi_range_read_const_call_record : public Sql_alloc Cost_estimate cost; ha_rows max_index_blocks; ha_rows max_row_blocks; + ulong call_number; }; /* @@ -257,6 +258,7 @@ void dump_mrr_info_calls(List *mrr_list, irc_wrapper.add("max_index_blocks", irc->max_index_blocks); irc_wrapper.add("max_row_blocks", irc->max_row_blocks); + irc_wrapper.add("call_number", irc->call_number); } } @@ -818,11 +820,13 @@ void Optimizer_context_recorder::record_multi_range_read_info_const( if (current_thd->lex->explain->is_query_plan_ready()) return; + mrr_counter++; auto *range_ctx= new (mem_root) Multi_range_read_const_call_record; if (unlikely(!range_ctx)) return; // OOM + range_ctx->call_number= mrr_counter; const char *index_name= tbl->table->key_info[keynr].name.str; if (!(range_ctx->idx_name= strdup_root(mem_root, index_name))) return; // OOM @@ -1285,8 +1289,7 @@ static int parse_range_context(MEM_ROOT *mem_root, json_engine_t *je, String *er Read_named_member array[]= { {"index_name", Read_string(mem_root, &out->idx_name), false}, {"ranges", Read_array_of_strings(mem_root, &out->range_list), false}, - {"num_rows", - Read_non_neg_integer(&out->rows), + {"num_rows", Read_non_neg_integer(&out->rows), false}, {"cost", Read_range_cost_estimate(mem_root, &out->cost), false}, {"max_index_blocks", @@ -1295,6 +1298,8 @@ static int parse_range_context(MEM_ROOT *mem_root, json_engine_t *je, String *er {"max_row_blocks", Read_non_neg_integer(&out->max_row_blocks), false}, + {"call_number", + Read_non_neg_integer(&out->call_number), false}, {NULL, Read_double(NULL), true}}; return parse_context_obj_from_json_array(je, err_buf, err_msg, array); @@ -1516,6 +1521,7 @@ bool Optimizer_context_replay::infuse_range_stats( if (!has_records() || !is_base_table(table->pos_in_table_list)) return true; + mrr_counter++; KEY *keyinfo= table->key_info + keynr; const char *idx_name= keyinfo->name.str; const KEY_PART_INFO *key_part= keyinfo->key_part; @@ -1544,6 +1550,9 @@ bool Optimizer_context_replay::infuse_range_stats( List_iterator range_ctx_itr(mrr_const_calls); while (Multi_range_read_const_call_record *range_ctx= range_ctx_itr++) { + if (range_ctx->call_number != mrr_counter) + continue; + List_iterator range_itr(range_ctx->range_list); seq_it= seq_if->init((void *) seq, 0, 0); bool matched= true; diff --git a/sql/opt_context_store_replay.h b/sql/opt_context_store_replay.h index 1ec2d7a84e80a..c9dc877f4a1fe 100644 --- a/sql/opt_context_store_replay.h +++ b/sql/opt_context_store_replay.h @@ -88,9 +88,12 @@ class Optimizer_context_recorder table_context_for_store *get_table_context(const TABLE_LIST *tbl); static const uchar *get_tbl_ctx_key(const void *entry_, size_t *length, my_bool flags); + /* + counter that tracks record_multi_range_read_info_const() calls + */ + ulong mrr_counter= 0; }; - /* Save the collected context into optimizer_context IS table */ bool store_optimizer_context(THD *thd); @@ -150,6 +153,10 @@ class Optimizer_context_replay bool infuse_table_rows(const TABLE *tbl, ha_rows *rows); THD *thd; + /* + counter that tracks infuse_range_stats() calls + */ + ulong mrr_counter= 0; /* Statistics that tables had before we've replaced them with values from the saved context. To be used to restore the original values.