diff --git a/mysql-test/r/index_merge_myisam.result b/mysql-test/r/index_merge_myisam.result index c907997573a..9820170aa59 100644 --- a/mysql-test/r/index_merge_myisam.result +++ b/mysql-test/r/index_merge_myisam.result @@ -74,7 +74,7 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 17 Using sort_union(i1,i2); Using where explain select * from t0 where key2 = 45 or key1 <=> null; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t0 range i1,i2 i2 4 NULL 1 Using where +1 SIMPLE t0 range i1,i2 i2 4 NULL 1 Using index condition explain select * from t0 where key2 = 45 or key1 is not null; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL i1,i2 NULL NULL NULL 1024 Using where diff --git a/mysql-test/r/innodb_ext_key.result b/mysql-test/r/innodb_ext_key.result index 9140f306f77..cae402a9f12 100644 --- a/mysql-test/r/innodb_ext_key.result +++ b/mysql-test/r/innodb_ext_key.result @@ -1002,7 +1002,7 @@ insert into t2 (b) values (null), (null), (null); set optimizer_switch='extended_keys=on'; explain select a from t1 where b is null order by a desc limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ref b b 9 const 2 Using where; Using filesort +1 SIMPLE t1 index b PRIMARY 8 NULL 3 Using where select a from t1 where b is null order by a desc limit 2; a 3 diff --git a/mysql-test/r/join_outer.result b/mysql-test/r/join_outer.result index c4770182598..36c2f1898f8 100644 --- a/mysql-test/r/join_outer.result +++ b/mysql-test/r/join_outer.result @@ -2090,10 +2090,10 @@ SELECT t1.b, t2.c, t2.d FROM t2 LEFT JOIN t1 ON t2.c = t1.a WHERE t1.pk BETWEEN 5 AND 6 AND t1.b IS NULL OR t1.b = 5 ORDER BY t1.b; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ref PRIMARY,idx idx 4 const 2 100.00 Using where; Using filesort +1 SIMPLE t1 ref PRIMARY,idx idx 4 const 2 100.00 Using where 1 SIMPLE t2 ref c c 5 test.t1.a 2 100.00 Warnings: -Note 1003 select `test`.`t1`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` join `test`.`t1` where ((`test`.`t2`.`c` = `test`.`t1`.`a`) and (((`test`.`t1`.`pk` between 5 and 6) and isnull(`test`.`t1`.`b`)) or (`test`.`t1`.`b` = 5))) order by `test`.`t1`.`b` +Note 1003 select `test`.`t1`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` join `test`.`t1` where ((`test`.`t2`.`c` = `test`.`t1`.`a`) and (`test`.`t1`.`b` = 5)) order by `test`.`t1`.`b` SELECT t1.b, t2.c, t2.d FROM t2 LEFT JOIN t1 ON t2.c = t1.a WHERE t1.pk BETWEEN 5 AND 6 AND t1.b IS NULL OR t1.b = 5 ORDER BY t1.b; diff --git a/mysql-test/r/join_outer_jcl6.result b/mysql-test/r/join_outer_jcl6.result index 862fc194a7a..6f3da3efdd7 100644 --- a/mysql-test/r/join_outer_jcl6.result +++ b/mysql-test/r/join_outer_jcl6.result @@ -2101,10 +2101,10 @@ SELECT t1.b, t2.c, t2.d FROM t2 LEFT JOIN t1 ON t2.c = t1.a WHERE t1.pk BETWEEN 5 AND 6 AND t1.b IS NULL OR t1.b = 5 ORDER BY t1.b; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ref PRIMARY,idx idx 4 const 2 100.00 Using where; Using filesort +1 SIMPLE t1 ref PRIMARY,idx idx 4 const 2 100.00 Using where 1 SIMPLE t2 ref c c 5 test.t1.a 2 100.00 Warnings: -Note 1003 select `test`.`t1`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` join `test`.`t1` where ((`test`.`t2`.`c` = `test`.`t1`.`a`) and (((`test`.`t1`.`pk` between 5 and 6) and isnull(`test`.`t1`.`b`)) or (`test`.`t1`.`b` = 5))) order by `test`.`t1`.`b` +Note 1003 select `test`.`t1`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` join `test`.`t1` where ((`test`.`t2`.`c` = `test`.`t1`.`a`) and (`test`.`t1`.`b` = 5)) order by `test`.`t1`.`b` SELECT t1.b, t2.c, t2.d FROM t2 LEFT JOIN t1 ON t2.c = t1.a WHERE t1.pk BETWEEN 5 AND 6 AND t1.b IS NULL OR t1.b = 5 ORDER BY t1.b; diff --git a/mysql-test/r/range.result b/mysql-test/r/range.result index f2ad42ebc8d..fe528e1b2e9 100644 --- a/mysql-test/r/range.result +++ b/mysql-test/r/range.result @@ -2196,3 +2196,48 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t3 const PRIMARY PRIMARY 4 const 1 1 SIMPLE t2 range a a 5 NULL 1 Using where; Using index drop table t1,t2,t3; +# +# MDEV-6480: Remove conditions for which range optimizer returned SEL_ARG::IMPOSSIBLE. +# +create table t1(a int); +insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t2 (a int, b int, c int, key(a), key(b)); +insert into t2 +select +A.a + B.a* 10 + C.a * 100, +A.a + B.a* 10 + C.a * 100, +12345 +from +t1 A, t1 B, t1 C; +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or a<44; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range a,b a 5 NULL 43 100.00 Using index condition +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`a` < 44) +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where a < 44 or (b > 25 and b < 15); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range a,b a 5 NULL 43 100.00 Using index condition +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`a` < 44) +# Here, conditions b will not be removed, because "c<44" is not sargable +# and hence (b.. and .. b) part is not analyzed at all: +explain extended select * from t2 where c < 44 or (b > 25 and b < 15); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL b NULL NULL NULL 1000 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where ((`test`.`t2`.`c` < 44) or ((`test`.`t2`.`b` > 25) and (`test`.`t2`.`b` < 15))) +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or c < 44; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL b NULL NULL NULL 1000 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where ((`test`.`t2`.`c` < 44)) +# Try a case where both OR parts produce SEL_ARG::IMPOSSIBLE: +explain extended select * from t2 where (b > 25 and b < 15) or (a>55 and a<44); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where 0 +drop table t1,t2; diff --git a/mysql-test/r/range_mrr_icp.result b/mysql-test/r/range_mrr_icp.result index 16b35448c50..62bea71173c 100644 --- a/mysql-test/r/range_mrr_icp.result +++ b/mysql-test/r/range_mrr_icp.result @@ -2198,4 +2198,49 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t3 const PRIMARY PRIMARY 4 const 1 1 SIMPLE t2 range a a 5 NULL 1 Using where; Using index drop table t1,t2,t3; +# +# MDEV-6480: Remove conditions for which range optimizer returned SEL_ARG::IMPOSSIBLE. +# +create table t1(a int); +insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t2 (a int, b int, c int, key(a), key(b)); +insert into t2 +select +A.a + B.a* 10 + C.a * 100, +A.a + B.a* 10 + C.a * 100, +12345 +from +t1 A, t1 B, t1 C; +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or a<44; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range a,b a 5 NULL 43 100.00 Using index condition; Rowid-ordered scan +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`a` < 44) +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where a < 44 or (b > 25 and b < 15); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range a,b a 5 NULL 43 100.00 Using index condition; Rowid-ordered scan +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (`test`.`t2`.`a` < 44) +# Here, conditions b will not be removed, because "c<44" is not sargable +# and hence (b.. and .. b) part is not analyzed at all: +explain extended select * from t2 where c < 44 or (b > 25 and b < 15); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL b NULL NULL NULL 1000 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where ((`test`.`t2`.`c` < 44) or ((`test`.`t2`.`b` > 25) and (`test`.`t2`.`b` < 15))) +# EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or c < 44; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL b NULL NULL NULL 1000 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where ((`test`.`t2`.`c` < 44)) +# Try a case where both OR parts produce SEL_ARG::IMPOSSIBLE: +explain extended select * from t2 where (b > 25 and b < 15) or (a>55 and a<44); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where 0 +drop table t1,t2; set optimizer_switch=@mrr_icp_extra_tmp; diff --git a/mysql-test/t/range.test b/mysql-test/t/range.test index 7b68f42c4cb..6249d2b5e4f 100644 --- a/mysql-test/t/range.test +++ b/mysql-test/t/range.test @@ -1745,3 +1745,35 @@ explain select * from t3, t2 where t2.a < t3.b and t3.a=1; --echo # The second table should use 'range': explain select * from t3, t2 where t3.b > t2.a and t3.a=1; drop table t1,t2,t3; + +--echo # +--echo # MDEV-6480: Remove conditions for which range optimizer returned SEL_ARG::IMPOSSIBLE. +--echo # +create table t1(a int); +insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t2 (a int, b int, c int, key(a), key(b)); +insert into t2 +select + A.a + B.a* 10 + C.a * 100, + A.a + B.a* 10 + C.a * 100, + 12345 +from + t1 A, t1 B, t1 C; + +--echo # EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or a<44; + +--echo # EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where a < 44 or (b > 25 and b < 15); + +--echo # Here, conditions b will not be removed, because "c<44" is not sargable +--echo # and hence (b.. and .. b) part is not analyzed at all: +explain extended select * from t2 where c < 44 or (b > 25 and b < 15); + +--echo # EXPLAIN EXTENDED should show that 'b > 25 and b < 15' is removed from the WHERE: +explain extended select * from t2 where (b > 25 and b < 15) or c < 44; + +--echo # Try a case where both OR parts produce SEL_ARG::IMPOSSIBLE: +explain extended select * from t2 where (b > 25 and b < 15) or (a>55 and a<44); + +drop table t1,t2; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 2616e044025..99c731d1541 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -829,6 +829,12 @@ public: */ bool remove_jump_scans; + /* + TRUE <=> Range analyzer should remove parts of condition that are found + to be always FALSE. + */ + bool remove_false_where_parts; + /* used_key_no -> table_key_no translation table. Only makes sense if using_real_indexes==TRUE @@ -908,7 +914,7 @@ static SEL_TREE * get_mm_parts(RANGE_OPT_PARAM *param,COND *cond_func,Field *fie static SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param,COND *cond_func,Field *field, KEY_PART *key_part, Item_func::Functype type,Item *value); -static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond); +static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond); static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts); static ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, @@ -2941,7 +2947,8 @@ static int fill_used_fields_bitmap(PARAM *param) int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, table_map prev_tables, ha_rows limit, bool force_quick_range, - bool ordered_output) + bool ordered_output, + bool remove_false_parts_of_where) { uint idx; double scan_time; @@ -3000,6 +3007,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, param.imerge_cost_buff_size= 0; param.using_real_indexes= TRUE; param.remove_jump_scans= TRUE; + param.remove_false_where_parts= remove_false_parts_of_where; param.force_default_mrr= ordered_output; param.possible_keys.clear_all(); @@ -3073,7 +3081,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, if (cond) { - if ((tree= get_mm_tree(¶m,cond))) + if ((tree= get_mm_tree(¶m, &cond))) { if (tree->type == SEL_TREE::IMPOSSIBLE) { @@ -3415,7 +3423,7 @@ double records_in_column_ranges(PARAM *param, uint idx, TRUE otherwise */ -bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond) +bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond) { uint keynr; uint max_quick_key_parts= 0; @@ -3425,7 +3433,7 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond) table->cond_selectivity= 1.0; - if (!cond || table_records == 0) + if (!*cond || table_records == 0) DBUG_RETURN(FALSE); if (table->pos_in_table_list->schema_table) @@ -3529,6 +3537,7 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond) param.old_root= thd->mem_root; param.table= table; param.is_ror_scan= FALSE; + param.remove_false_where_parts= true; if (create_key_parts_for_pseudo_indexes(¶m, used_fields)) goto free_alloc; @@ -3606,7 +3615,7 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond) ulong check_rows= MY_MIN(thd->variables.optimizer_selectivity_sampling_limit, (ulong) (table_records * SELECTIVITY_SAMPLING_SHARE)); - if (cond && check_rows > SELECTIVITY_SAMPLING_THRESHOLD && + if (*cond && check_rows > SELECTIVITY_SAMPLING_THRESHOLD && thd->variables.optimizer_use_condition_selectivity > 4) { find_selective_predicates_list_processor_data *dt= @@ -3617,8 +3626,8 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond) DBUG_RETURN(TRUE); dt->list.empty(); dt->table= table; - if (cond->walk(&Item::find_selective_predicates_list_processor, 0, - (uchar*) dt)) + if ((*cond)->walk(&Item::find_selective_predicates_list_processor, 0, + (uchar*) dt)) DBUG_RETURN(TRUE); if (dt->list.elements > 0) { @@ -3951,6 +3960,8 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) /* range_par->cond doesn't need initialization */ range_par->prev_tables= range_par->read_tables= 0; range_par->current_table= table->map; + /* It should be possible to switch the following ON: */ + range_par->remove_false_where_parts= false; range_par->keys= 1; // one index range_par->using_real_indexes= FALSE; @@ -3967,7 +3978,7 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) SEL_TREE *tree; int res; - tree= get_mm_tree(range_par, pprune_cond); + tree= get_mm_tree(range_par, &pprune_cond); if (!tree) goto all_used; @@ -7855,15 +7866,33 @@ static SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param, DBUG_RETURN(ftree); } - /* make a select tree of all keys in condition */ +/* + make a select tree of all keys in condition + + @param param Context + @param cond INOUT condition to perform range analysis on. -static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond) + @detail + Range analysis may infer that some conditions are never true. + - If the condition is never true, SEL_TREE(type=IMPOSSIBLE) is returned + - if parts of condition are never true, the function may remove these parts + from the condition 'cond'. Sometimes, this will cause the condition to + be substituted for something else. + + + @return + NULL - Could not infer anything from condition cond. + SEL_TREE with type=IMPOSSIBLE - condition can never be true. +*/ + +static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) { SEL_TREE *tree=0; SEL_TREE *ftree= 0; Item_field *field_item= 0; bool inv= FALSE; Item *value= 0; + Item *cond= *cond_ptr; DBUG_ENTER("get_mm_tree"); if (cond->type() == Item::COND_ITEM) @@ -7876,31 +7905,75 @@ static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond) Item *item; while ((item=li++)) { - SEL_TREE *new_tree= get_mm_tree(param,item); + SEL_TREE *new_tree= get_mm_tree(param,li.ref()); if (param->statement_should_be_aborted()) DBUG_RETURN(NULL); tree= tree_and(param,tree,new_tree); if (tree && tree->type == SEL_TREE::IMPOSSIBLE) + { + /* + Do not remove 'item' from 'cond'. We return a SEL_TREE::IMPOSSIBLE + and that is sufficient for the caller to see that the whole + condition is never true. + */ break; + } } } else { // COND OR - tree= get_mm_tree(param,li++); + bool replace_cond= false; + Item *replacement_item= li++; + tree= get_mm_tree(param, li.ref()); if (param->statement_should_be_aborted()) DBUG_RETURN(NULL); if (tree) { + if (tree->type == SEL_TREE::IMPOSSIBLE && + param->remove_false_where_parts) + { + /* See the other li.remove() call below */ + li.remove(); + if (((Item_cond*)cond)->argument_list()->elements <= 1) + replace_cond= true; + } + Item *item; while ((item=li++)) { - SEL_TREE *new_tree=get_mm_tree(param,item); + SEL_TREE *new_tree=get_mm_tree(param,li.ref()); if (new_tree == NULL || param->statement_should_be_aborted()) DBUG_RETURN(NULL); tree= tree_or(param,tree,new_tree); if (tree == NULL || tree->type == SEL_TREE::ALWAYS) + { + replacement_item= *li.ref(); break; + } + + if (new_tree && new_tree->type == SEL_TREE::IMPOSSIBLE && + param->remove_false_where_parts) + { + /* + This is a condition in form + + cond = item1 OR ... OR item_i OR ... itemN + + and item_i produces SEL_TREE(IMPOSSIBLE). We should remove item_i + from cond. This may cause 'cond' to become a degenerate, + one-way OR. In that case, we replace 'cond' with the remaining + item_i. + */ + li.remove(); + if (((Item_cond*)cond)->argument_list()->elements <= 1) + replace_cond= true; + } + else + replacement_item= *li.ref(); } + + if (replace_cond) + *cond_ptr= replacement_item; } } DBUG_RETURN(tree); @@ -10617,6 +10690,7 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, param->table->quick_condition_rows= MY_MIN(param->table->quick_condition_rows, rows); param->table->quick_rows[keynr]= rows; + param->table->quick_costs[keynr]= cost->total_cost(); } } /* Figure out if the key scan is ROR (returns rows in ROWID order) or not */ diff --git a/sql/opt_range.h b/sql/opt_range.h index 1ca245ea420..f602408ea82 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -994,7 +994,7 @@ class SQL_SELECT :public Sql_alloc { { key_map tmp; tmp.set_all(); - return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE) < 0; + return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE, FALSE) < 0; } /* RETURN @@ -1011,7 +1011,7 @@ class SQL_SELECT :public Sql_alloc { } int test_quick_select(THD *thd, key_map keys, table_map prev_tables, ha_rows limit, bool force_quick_range, - bool ordered_output); + bool ordered_output, bool remove_false_parts_of_where); }; @@ -1036,7 +1036,7 @@ SQL_SELECT *make_select(TABLE *head, table_map const_tables, table_map read_tables, COND *conds, bool allow_null_cond, int *error); -bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item *cond); +bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond); #ifdef WITH_PARTITION_STORAGE_ENGINE bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 1fedf12b595..9c87ddcc4b4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -69,7 +69,7 @@ struct st_sargable_param; static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array); static bool make_join_statistics(JOIN *join, List &leaves, - COND *conds, DYNAMIC_ARRAY *keyuse); + DYNAMIC_ARRAY *keyuse); static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, JOIN_TAB *join_tab, uint tables, COND *conds, @@ -1338,7 +1338,7 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S /* Calculate how to do the join */ THD_STAGE_INFO(thd, stage_statistics); - if (make_join_statistics(this, select_lex->leaf_tables, conds, &keyuse) || + if (make_join_statistics(this, select_lex->leaf_tables, &keyuse) || thd->is_fatal_error) { DBUG_PRINT("error",("Error: make_join_statistics() failed")); @@ -3355,7 +3355,8 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, select->head=table; table->reginfo.impossible_range=0; if ((error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0, - limit, 0, FALSE)) == 1) + limit, 0, FALSE, + TRUE /* remove_where_parts*/)) == 1) DBUG_RETURN(select->quick->records); if (error == -1) { @@ -3393,7 +3394,7 @@ typedef struct st_sargable_param static bool make_join_statistics(JOIN *join, List &tables_list, - COND *conds, DYNAMIC_ARRAY *keyuse_array) + DYNAMIC_ARRAY *keyuse_array) { int error= 0; TABLE *table; @@ -3597,10 +3598,10 @@ make_join_statistics(JOIN *join, List &tables_list, } } - if (conds || outer_join) + if (join->conds || outer_join) { if (update_ref_and_keys(join->thd, keyuse_array, stat, join->table_count, - conds, ~outer_join, join->select_lex, &sargables)) + join->conds, ~outer_join, join->select_lex, &sargables)) goto error; /* Keyparts without prefixes may be useful if this JOIN is a subquery, and @@ -3844,8 +3845,9 @@ make_join_statistics(JOIN *join, List &tables_list, } join->impossible_where= false; - if (conds && const_count) - { + if (join->conds && const_count) + { + Item* &conds= join->conds; conds->update_used_tables(); conds= remove_eq_conds(join->thd, conds, &join->cond_value); if (conds && conds->type() == Item::COND_ITEM && @@ -3857,7 +3859,7 @@ make_join_statistics(JOIN *join, List &tables_list, join->impossible_where= true; conds=new Item_int((longlong) 0,1); } - join->conds= conds; + join->cond_equal= NULL; if (conds) { @@ -3942,12 +3944,18 @@ make_join_statistics(JOIN *join, List &tables_list, { select= make_select(s->table, found_const_table_map, found_const_table_map, - *s->on_expr_ref ? *s->on_expr_ref : conds, + *s->on_expr_ref ? *s->on_expr_ref : join->conds, 1, &error); if (!select) goto error; records= get_quick_record_count(join->thd, select, s->table, &s->const_keys, join->row_limit); + /* Range analyzer could modify the condition. */ + if (*s->on_expr_ref) + *s->on_expr_ref= select->cond; + else + join->conds= select->cond; + s->quick=select->quick; s->needed_reg=select->needed_reg; select->quick=0; @@ -3958,7 +3966,7 @@ make_join_statistics(JOIN *join, List &tables_list, if (join->thd->variables.optimizer_use_condition_selectivity > 1) calculate_cond_selectivity_for_table(join->thd, s->table, *s->on_expr_ref ? - *s->on_expr_ref : conds); + s->on_expr_ref : &join->conds); if (s->table->reginfo.impossible_range) { impossible_range= TRUE; @@ -9658,7 +9666,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt), 0, - FALSE) < 0) + FALSE, FALSE) < 0) { /* Before reporting "Impossible WHERE" for the whole query @@ -9672,7 +9680,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt),0, - FALSE) < 0) + FALSE, FALSE) < 0) DBUG_RETURN(1); // Impossible WHERE } else @@ -18496,7 +18504,7 @@ test_if_quick_select(JOIN_TAB *tab) tab->select->quick=0; return tab->select->test_quick_select(tab->join->thd, tab->keys, (table_map) 0, HA_POS_ERROR, 0, - FALSE); + FALSE, /*remove where parts*/FALSE); } @@ -20169,7 +20177,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, OPTION_FOUND_ROWS) ? HA_POS_ERROR : tab->join->unit->select_limit_cnt,0, - TRUE) <= 0; + TRUE, FALSE) <= 0; if (res) { select->cond= save_cond; @@ -20216,7 +20224,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, !(table->file->index_flags(best_key, 0, 1) & HA_CLUSTERED_INDEX))) goto use_filesort; - if (select && + if (select && // psergey: why doesn't this use a quick? table->quick_keys.is_set(best_key) && best_key != ref_key) { key_map map; @@ -20227,7 +20235,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt, - TRUE, FALSE); + TRUE, FALSE, FALSE); } order_direction= best_key_direction; /* @@ -24689,6 +24697,109 @@ void JOIN::cache_const_exprs() } } + +/* + Get a cost of reading rows_limit rows through index keynr. + + @detail + - If there is a quick select, we try to use it. + - if there is a ref(const) access, we try to use it, too. + - quick and ref(const) use different cost formulas, so if both are possible + we should make a cost-based choice. + + @return + true There was a possible quick or ref access, its cost is in the OUT + parameters. + false No quick or ref(const) possible (and so, the caller will attempt + to use a full index scan on this index). +*/ + +static bool get_range_limit_read_cost(const JOIN_TAB *tab, + const TABLE *table, + uint keynr, + ha_rows rows_limit, + double *read_time) +{ + bool res= false; + /* + We need to adjust the estimates if we had a quick select (or ref(const)) on + index keynr. + */ + if (table->quick_keys.is_set(keynr)) + { + /* + Start from quick select's rows and cost. These are always cheaper than + full index scan/cost. + */ + double best_rows= table->quick_rows[keynr]; + double best_cost= table->quick_costs[keynr]; + + /* + Check if ref(const) access was possible on this index. + */ + if (tab) + { + key_part_map const_parts= 0; + key_part_map map= 1; + uint kp; + /* Find how many key parts would be used by ref(const) */ + for (kp=0; kp < MAX_REF_PARTS; map=map << 1, kp++) + { + if (!(table->const_key_parts[keynr] & map)) + break; + const_parts |= map; + } + + if (kp > 0) + { + ha_rows ref_rows; + /* + Two possible cases: + 1. ref(const) uses the same #key parts as range access. + 2. ref(const) uses fewer key parts, becasue there is a + range_cond(key_part+1). + */ + if (kp == table->quick_key_parts[keynr]) + ref_rows= table->quick_rows[keynr]; + else + ref_rows= table->key_info[keynr].actual_rec_per_key(kp-1); + + if (ref_rows > 0) + { + double tmp= ref_rows; + /* Reuse the cost formula from best_access_path: */ + set_if_smaller(tmp, (double) tab->join->thd->variables.max_seeks_for_key); + if (table->covering_keys.is_set(keynr)) + tmp= table->file->keyread_time(keynr, 1, (ha_rows) tmp); + else + tmp= table->file->read_time(keynr, 1, + (ha_rows) MY_MIN(tmp,tab->worst_seeks)); + if (tmp < best_cost) + { + best_cost= tmp; + best_rows= ref_rows; + } + } + } + } + + if (best_rows > rows_limit) + { + /* + LIMIT clause specifies that we will need to read fewer records than + quick select will return. Assume that quick select's cost is + proportional to the number of records we need to return (e.g. if we + only need 1/3rd of records, it will cost us 1/3rd of quick select's + read time) + */ + best_cost *= rows_limit / best_rows; + } + *read_time= best_cost; + res= true; + } + return res; +} + /** Find a cheaper access key than a given @a key @@ -24782,6 +24893,11 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, } else read_time= table->file->scan_time(); + + /* + TODO: add cost of sorting here. + */ + read_time += COST_EPS; /* Calculate the selectivity of the ref_key for REF_ACCESS. For @@ -24941,6 +25057,14 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, */ index_scan_time= select_limit/rec_per_key * MY_MIN(rec_per_key, table->file->scan_time()); + double range_scan_time; + if (get_range_limit_read_cost(tab, table, nr, select_limit, + &range_scan_time)) + { + if (range_scan_time < index_scan_time) + index_scan_time= range_scan_time; + } + if ((ref_key < 0 && (group || table->force_index || is_covering)) || index_scan_time < read_time) { diff --git a/sql/table.h b/sql/table.h index b57e9c7227d..bbe4b181339 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1113,6 +1113,7 @@ public: and max #key parts that range access would use. */ ha_rows quick_rows[MAX_KEY]; + double quick_costs[MAX_KEY]; /* Bitmaps of key parts that =const for the duration of join execution. If