From 275a4b354b57d81cbaa7e9bf7f422856a9b88a36 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Wed, 3 Jun 2009 17:10:45 +0400 Subject: [PATCH 01/27] MWL#17: Table elimination - First code. Elimination works for simple cases, passes the testsuite. - Known issues: = No elimination is done for aggregate functions. = EXPLAIN EXTENDED shows eliminated tables (I think it better not) = No benchmark yet = The code needs some polishing. mysql-test/r/table_elim.result: MWL#17: Table elimination - Testcases mysql-test/t/table_elim.test: MWL#17: Table elimination - Testcases sql/sql_select.cc: MWL#17: Table elimination sql/sql_select.h: MWL#17: Table elimination - Added JOIN_TAB::eliminated (is JOIN_TAB the best place to store this flag?) sql/table.h: MWL#17: Table elimination - ADded NESTED_JOIN::n_tables. We need to have the number of real tables remaining in an outer join nest. --- mysql-test/r/table_elim.result | 56 ++++++ mysql-test/t/table_elim.test | 52 ++++++ sql/sql_select.cc | 329 ++++++++++++++++++++++++++++++++- sql/sql_select.h | 3 + sql/table.h | 2 + 5 files changed, 434 insertions(+), 8 deletions(-) create mode 100644 mysql-test/r/table_elim.result create mode 100644 mysql-test/t/table_elim.test diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result new file mode 100644 index 00000000000..c424c5824a4 --- /dev/null +++ b/mysql-test/r/table_elim.result @@ -0,0 +1,56 @@ +drop table if exists t0, t1, t2, t3; +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); +create table t0 as select * from t1; +create table t2 (a int primary key, b int) +as select a, a as b from t1 where a in (1,2); +create table t3 (a int primary key, b int) +as select a, a as b from t1 where a in (1,3); +# This will be eliminated: +explain select t1.a from t1 left join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +select t1.a from t1 left join t2 on t2.a=t1.a; +a +0 +1 +2 +3 +# This will not be eliminated as t2.b is in in select list: +explain select * from t1 left join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 +# This will not be eliminated as t2.b is in in order list: +explain select t1.a from t1 left join t2 on t2.a=t1.a order by t2.b; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 +# This will not be eliminated as t2.b is in group list: +explain select t1.a from t1 left join t2 on t2.a=t1.a group by t2.b; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 +# This will not be eliminated as t2.b is in the WHERE +explain select t1.a from t1 left join t2 on t2.a=t1.a where t2.b < 3 or t2.b is null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 Using where +# Elimination of multiple tables: +explain select t1.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +# Elimination of multiple tables (2): +explain select t1.a from t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +# Elimination when done within an outer join nest: +explain +select t0.* +from +t0 left join (t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and +t3.a=t1.a) on t0.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 4 +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +drop table t0, t1, t2, t3; diff --git a/mysql-test/t/table_elim.test b/mysql-test/t/table_elim.test new file mode 100644 index 00000000000..c540db884ea --- /dev/null +++ b/mysql-test/t/table_elim.test @@ -0,0 +1,52 @@ +# +# Table elimination (MWL#17) tests +# +--disable_warnings +drop table if exists t0, t1, t2, t3; +--enable_warnings + +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); +create table t0 as select * from t1; + +create table t2 (a int primary key, b int) + as select a, a as b from t1 where a in (1,2); + +create table t3 (a int primary key, b int) + as select a, a as b from t1 where a in (1,3); + +--echo # This will be eliminated: +explain select t1.a from t1 left join t2 on t2.a=t1.a; + +select t1.a from t1 left join t2 on t2.a=t1.a; + +--echo # This will not be eliminated as t2.b is in in select list: +explain select * from t1 left join t2 on t2.a=t1.a; + +--echo # This will not be eliminated as t2.b is in in order list: +explain select t1.a from t1 left join t2 on t2.a=t1.a order by t2.b; + +--echo # This will not be eliminated as t2.b is in group list: +explain select t1.a from t1 left join t2 on t2.a=t1.a group by t2.b; + +## TODO: Aggregate functions prevent table elimination ATM. + +--echo # This will not be eliminated as t2.b is in the WHERE +explain select t1.a from t1 left join t2 on t2.a=t1.a where t2.b < 3 or t2.b is null; + +--echo # Elimination of multiple tables: +explain select t1.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a; + +--echo # Elimination of multiple tables (2): +explain select t1.a from t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; + +--echo # Elimination when done within an outer join nest: +explain +select t0.* +from + t0 left join (t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and + t3.a=t1.a) on t0.a=t1.a; + + +drop table t0, t1, t2, t3; + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 302b23124cf..a615c83f2f4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -42,6 +42,11 @@ #define TMP_ENGINE_HTON myisam_hton #endif +#define FT_KEYPART (MAX_REF_PARTS+10) +/* Values in optimize */ +#define KEY_OPTIMIZE_EXISTS 1 +#define KEY_OPTIMIZE_REF_OR_NULL 2 + const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "MAYBE_REF","ALL","range","index","fulltext", "ref_or_null","unique_subquery","index_subquery", @@ -2468,6 +2473,304 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ } + +bool has_eq_ref_access_candidate(TABLE *table, table_map can_refer_to_these) +{ + KEYUSE *keyuse= table->reginfo.join_tab->keyuse; + if (keyuse) + { + /* + walk through all of the KEYUSE elements and + - locate unique keys + - check if we have eq_ref access for them + TODO any other reqs? + loops are constructed like in best_access_path + */ + while (keyuse->table == table) + { + uint key= keyuse->key; + key_part_map bound_parts=0; + bool ft_key= test(keyuse->keypart == FT_KEYPART); + + do /* For each keypart and each way to read it */ + { + if (!(keyuse->used_tables & ~can_refer_to_these) && + !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) + { + bound_parts |= keyuse->keypart_map; + } + keyuse++; + } while (keyuse->table && keyuse->key == key); + + KEY *keyinfo= table->key_info + key; + if (!ft_key && + ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME) && + bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) + { + return TRUE; + } + } + } + return FALSE; +} + + +static void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, + table_map *const_tables) +{ + JOIN_TAB *tab= table->reginfo.join_tab; + if (!(*const_tables & tab->table->map)) + { + DBUG_PRINT("info", ("Eliminated table %s", table->alias)); + tab->type= JT_CONST; + tab->eliminated= TRUE; + *const_tables |= table->map; + join->const_table_map|= table->map; + set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); + } +} + + +/* + Now on to traversal. There can be a situation like this: + + FROM t1 + LEFT JOIN t2 ON cond(t1,t2) + LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*) + LEFT JOIN t4 ON cond(..., possibly-t2) + + Besides that, simplify_joins() may have created back references, so when + we're e.g. looking at outer join (*) we need to look both forward and + backward to check if there are any references in preceding/following + outer joins' + + TODO would it create only following-sibling references or + preceding-sibling as well? + And if not, should we rely on that? + +*/ + +int +eliminate_tables_for_join_list(JOIN *join, List *join_list, + table_map used_tables_elsewhere, + uint *const_tbl_count, table_map *const_tables) +{ + List_iterator it(*join_list); + table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca + table_map used_tables_on_left; + TABLE_LIST *tbl; + int i, n_tables; + int eliminated=0; + + /* Collect the reverse-bitmap-array */ + for (i=0; (tbl= it++); i++) + { + used_tables_on_right[i]= 0; + if (tbl->on_expr) + used_tables_on_right[i]= tbl->on_expr->used_tables(); + if (tbl->nested_join) + used_tables_on_right[i]= tbl->nested_join->used_tables; + } + n_tables= i; + + for (i= n_tables - 2; i > 0; i--) + used_tables_on_right[i] |= used_tables_on_right[i+1]; + + it.rewind(); + + /* Walk through tables and join nests and see if we can eliminate them */ + used_tables_on_left= 0; + i= 1; + while ((tbl= it++)) + { + table_map tables_used_outside= used_tables_on_left | + used_tables_on_right[i] | + used_tables_elsewhere; + table_map cur_tables; + + if (tbl->nested_join) + { + DBUG_ASSERT(tbl->on_expr); + /* + There can be cases where table removal is applicable for tables + within the outer join but not for the outer join itself. Ask to + remove the children first. + + TODO: NoHopelessEliminationAttempts: the below call can return + information about whether it would make any sense to try removing + this entire outer join nest. + */ + int eliminated_in_children= + eliminate_tables_for_join_list(join, &tbl->nested_join->join_list, + tables_used_outside, + const_tbl_count, const_tables); + tbl->nested_join->n_tables -=eliminated_in_children; + cur_tables= tbl->nested_join->used_tables; + if (!(cur_tables & tables_used_outside)) + { + /* + Check if all embedded tables together can produce at most one + record combination. This is true when + - each of them has one_match(outer-tables) property + (this is a stronger condition than all of them together having + this property but that's irrelevant here) + - there are no outer joins among them + (except for the case of outer join which has all inner tables + to be constant and is guaranteed to produce only one record. + that record will be null-complemented) + */ + bool one_match= TRUE; + List_iterator it2(tbl->nested_join->join_list); + TABLE_LIST *inner; + while ((inner= it2++)) + { + /* + Bail out if we see an outer join (TODO: handle the above + null-complemntated-rows-only case) + */ + if (inner->on_expr) + { + one_match= FALSE; + break; + } + + if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts + !has_eq_ref_access_candidate(inner->table, + ~tbl->nested_join->used_tables)) + { + one_match= FALSE; + break; + } + } + if (one_match) + { + it2.rewind(); + while ((inner= it2++)) + { + mark_table_as_eliminated(join, inner->table, const_tbl_count, + const_tables); + } + eliminated += tbl->nested_join->join_list.elements; + //psergey-todo: do we need to do anything about removing the join + //nest? + } + else + { + eliminated += eliminated_in_children; + } + } + } + else if (tbl->on_expr) + { + cur_tables= tbl->on_expr->used_tables(); + /* Check and remove */ + if (!(tbl->table->map & tables_used_outside) && + has_eq_ref_access_candidate(tbl->table, (table_map)-1)) + { + mark_table_as_eliminated(join, tbl->table, const_tbl_count, + const_tables); + eliminated += 1; + } + } + + /* Update bitmap of tables we've seen on the left */ + i++; + used_tables_on_left |= cur_tables; + } + return eliminated; +} + + +/* + Perform table elimination based on outer join + + SELECT * FROM t1 LEFT JOIN + (t2 JOIN t3) ON t3.primary_key=t1.col AND + t4.primary_key= t2.col + + CRITERIA FOR REMOVING ONE OJ NEST + we can't rely on sole presense of eq_refs. Because if we do, we'll miss + things like this: + + SELECT * FROM flights LEFT JOIN + (pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse) + + (no-polygamy schema/query but there can be many couples on the flight) + .. + + REMOVAL PROCESS + We can remove an inner side of an outer join if it there is a warranty + that it will produce not more than one record: + + ... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ... + + For nested outer joins: + - The process naturally occurs bottom-up (in order to remove an + outer-join we need to analyze its contents) + - If we failed to remove an outer join nest, it makes no sense to + try removing its ancestors, as the + ot LEFT JOIN it ON cond + pair may possibly produce two records (one record via match and + another one as access-method record). + + Q: If we haven't removed an OUTER JOIN, does it make sense to attempt + removing its ancestors? + A: No as the innermost outer join will produce two records => no ancestor + outer join nest will be able to provide the max_fanout==1 guarantee. + + psergey-todo: . +*/ + +static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const_tables) +{ + Item *item; + table_map used_tables; + DBUG_ENTER("eliminate_tables"); + if (!join->outer_join) + DBUG_VOID_RETURN; + + /* Find the tables that are referred to from WHERE/HAVING */ + used_tables= (join->conds? join->conds->used_tables() : 0) | + (join->having? join->having->used_tables() : 0); + + /* Add tables referred to from the select list */ + List_iterator it(join->fields_list); + while ((item= it++)) + used_tables |= item->used_tables(); + + /* Add tables referred to from ORDER BY and GROUP BY lists */ + ORDER *all_lists[]= { join->order, join->group_list}; + for (int i=0; i < 2; i++) + { + for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next) + used_tables |= (*(cur_list->item))->used_tables(); + } + + THD* thd= join->thd; + if (join->select_lex == &thd->lex->select_lex) + { + /* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */ + used_tables |= thd->table_map_for_update; + + /* Multi-table UPDATE: don't eliminate tables referred from SET statement */ + if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI) + { + List_iterator it2(thd->lex->value_list); + while ((item= it2++)) + used_tables |= item->used_tables(); + } + } + + if (((1 << join->tables) - 1) & ~used_tables) + { + /* There are some time tables that we probably could eliminate */ + eliminate_tables_for_join_list(join, join->join_list, used_tables, + const_tbl_count, const_tables); + } + DBUG_VOID_RETURN; +} + + /* This structure is used to collect info on potentially sargable predicates in order to check whether they become sargable after @@ -2823,6 +3126,10 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, } } + //psergey-todo: table elimination + eliminate_tables(join, &const_count, &found_const_table_map); + //:psergey-todo + /* Calc how many (possible) matched records in each table */ for (s=stat ; s < stat_end ; s++) @@ -2956,9 +3263,6 @@ typedef struct key_field_t { bool *cond_guard; /* See KEYUSE::cond_guard */ } KEY_FIELD; -/* Values in optimize */ -#define KEY_OPTIMIZE_EXISTS 1 -#define KEY_OPTIMIZE_REF_OR_NULL 2 /** Merge new key definitions to old ones, remove those not used in both. @@ -3563,7 +3867,6 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) } -#define FT_KEYPART (MAX_REF_PARTS+10) static void add_ft_keys(DYNAMIC_ARRAY *keyuse_array, @@ -6021,7 +6324,7 @@ make_outerjoin_info(JOIN *join) } if (!tab->first_inner) tab->first_inner= nested_join->first_nested; - if (++nested_join->counter < nested_join->join_list.elements) + if (++nested_join->counter < nested_join->n_tables) break; /* Table tab is the last inner table for nested join. */ nested_join->first_nested->last_inner= tab; @@ -8575,6 +8878,8 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) conds= simplify_joins(join, &nested_join->join_list, conds, top); used_tables= nested_join->used_tables; not_null_tables= nested_join->not_null_tables; + /* The following two might become unequal after table elimination: */ + nested_join->n_tables= nested_join->join_list.elements; } else { @@ -8733,7 +9038,7 @@ static uint build_bitmap_for_nested_joins(List *join_list, with anything) 2. we could run out bits in nested_join_map otherwise. */ - if (nested_join->join_list.elements != 1) + if (nested_join->n_tables != 1) { nested_join->nj_map= (nested_join_map) 1 << first_unused++; first_unused= build_bitmap_for_nested_joins(&nested_join->join_list, @@ -8894,7 +9199,7 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab) join->cur_embedding_map |= next_emb->nested_join->nj_map; } - if (next_emb->nested_join->join_list.elements != + if (next_emb->nested_join->n_tables != next_emb->nested_join->counter) break; @@ -8928,7 +9233,7 @@ static void restore_prev_nj_state(JOIN_TAB *last) { if (!(--last_emb->nested_join->counter)) join->cur_embedding_map&= ~last_emb->nested_join->nj_map; - else if (last_emb->nested_join->join_list.elements-1 == + else if (last_emb->nested_join->n_tables-1 == last_emb->nested_join->counter) join->cur_embedding_map|= last_emb->nested_join->nj_map; else @@ -16202,6 +16507,14 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, tmp3.length(0); quick_type= -1; + + //psergey-todo: + if (tab->eliminated) + { + used_tables|=table->map; + continue; + } + item_list.empty(); /* id */ item_list.push_back(new Item_uint((uint32) diff --git a/sql/sql_select.h b/sql/sql_select.h index 5e97185a7b9..d0c16558c91 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -210,6 +210,9 @@ typedef struct st_join_table { JOIN *join; /** Bitmap of nested joins this table is part of */ nested_join_map embedding_map; + + //psergey-todo: more justified place + bool eliminated; void cleanup(); inline bool is_using_loose_index_scan() diff --git a/sql/table.h b/sql/table.h index 97840d2a1c6..cc54daccf9d 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1626,6 +1626,8 @@ typedef struct st_nested_join Before each use the counters are zeroed by reset_nj_counters. */ uint counter; + /* Tables left after elimination */ + uint n_tables; nested_join_map nj_map; /* Bit used to identify this nested join*/ } NESTED_JOIN; From 9427771d302bbdfffdab416bcedef05aadcc898f Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Sun, 7 Jun 2009 22:24:03 +0400 Subject: [PATCH 02/27] MWL#17: Table Elimination - Fix trivial valgrind warning --- sql/sql_select.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a615c83f2f4..7a2fb857504 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2586,7 +2586,7 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, table_map tables_used_outside= used_tables_on_left | used_tables_on_right[i] | used_tables_elsewhere; - table_map cur_tables; + table_map cur_tables= 0; if (tbl->nested_join) { From cf028a54746d6b166e9ffe8fa1e2e08569fe0dc1 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 8 Jun 2009 17:55:46 +0400 Subject: [PATCH 03/27] Fix valgrind failure: provide an implementation of strmov_overlapp() that really can handle overlapping. include/m_string.h: Fix valgrind failure: provide an implementation of strmov_overlapp() that really can handle overlapping. --- include/m_string.h | 3 ++- libmysql/Makefile.shared | 3 ++- strings/Makefile.am | 8 ++++---- strings/strmov_overlapp.c | 26 ++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 strings/strmov_overlapp.c diff --git a/include/m_string.h b/include/m_string.h index c111a93c4bc..42161171eef 100644 --- a/include/m_string.h +++ b/include/m_string.h @@ -98,7 +98,8 @@ extern const double log_10[309]; #ifdef BAD_STRING_COMPILER #define strmov(A,B) (memccpy(A,B,0,INT_MAX)-1) #else -#define strmov_overlapp(A,B) strmov(A,B) +extern char *strmov_overlapp(char *dest, const char *src); +/* Warning: the following is likely not to work: */ #define strmake_overlapp(A,B,C) strmake(A,B,C) #endif diff --git a/libmysql/Makefile.shared b/libmysql/Makefile.shared index cbee8673164..62c44510d66 100644 --- a/libmysql/Makefile.shared +++ b/libmysql/Makefile.shared @@ -46,7 +46,8 @@ mystringsobjects = strmov.lo strxmov.lo strxnmov.lo strnmov.lo \ ctype-win1250ch.lo ctype-utf8.lo ctype-extra.lo \ ctype-ucs2.lo ctype-gb2312.lo ctype-gbk.lo \ ctype-sjis.lo ctype-tis620.lo ctype-ujis.lo \ - ctype-uca.lo xml.lo my_strtoll10.lo str_alloc.lo + ctype-uca.lo xml.lo my_strtoll10.lo str_alloc.lo \ + strmov_overlapp.lo mystringsextra= strto.c dbugobjects = dbug.lo # IT IS IN SAFEMALLOC.C sanity.lo diff --git a/strings/Makefile.am b/strings/Makefile.am index ddd41e627dc..740db4d7723 100644 --- a/strings/Makefile.am +++ b/strings/Makefile.am @@ -21,19 +21,19 @@ pkglib_LIBRARIES = libmystrings.a # Exact one of ASSEMBLER_X if ASSEMBLER_x86 ASRCS = strings-x86.s longlong2str-x86.s my_strtoll10-x86.s -CSRCS = bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c str_alloc.c longlong2str_asm.c my_strchr.c +CSRCS = bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c str_alloc.c longlong2str_asm.c my_strchr.c strmov_overlapp.c else if ASSEMBLER_sparc32 # These file MUST all be on the same line!! Otherwise automake # generats a very broken makefile ASRCS = bmove_upp-sparc.s strappend-sparc.s strend-sparc.s strinstr-sparc.s strmake-sparc.s strmov-sparc.s strnmov-sparc.s strstr-sparc.s -CSRCS = strcont.c strfill.c strcend.c is_prefix.c longlong2str.c bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c strxmov.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c my_strtoll10.c str_alloc.c my_strchr.c +CSRCS = strcont.c strfill.c strcend.c is_prefix.c longlong2str.c bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c strxmov.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c my_strtoll10.c str_alloc.c my_strchr.c strmov_overlapp.c else #no assembler ASRCS = # These file MUST all be on the same line!! Otherwise automake # generats a very broken makefile -CSRCS = strxmov.c bmove_upp.c strappend.c strcont.c strend.c strfill.c strcend.c is_prefix.c strstr.c strinstr.c strmake.c strnmov.c strmov.c longlong2str.c bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c my_strtoll10.c str_alloc.c my_strchr.c +CSRCS = strxmov.c bmove_upp.c strappend.c strcont.c strend.c strfill.c strcend.c is_prefix.c strstr.c strinstr.c strmake.c strnmov.c strmov.c longlong2str.c bfill.c bmove.c bmove512.c bchange.c strxnmov.c int2str.c str2int.c r_strinstr.c strtod.c bcmp.c strtol.c strtoul.c strtoll.c strtoull.c llstr.c strnlen.c ctype.c ctype-simple.c ctype-mb.c ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc_kr.c ctype-gb2312.c ctype-gbk.c ctype-sjis.c ctype-tis620.c ctype-ujis.c ctype-utf8.c ctype-ucs2.c ctype-uca.c ctype-win1250ch.c ctype-bin.c ctype-latin1.c my_vsnprintf.c xml.c decimal.c ctype-extra.c my_strtoll10.c str_alloc.c my_strchr.c strmov_overlapp.c endif endif @@ -54,7 +54,7 @@ EXTRA_DIST = ctype-big5.c ctype-cp932.c ctype-czech.c ctype-eucjpms.c ctype-euc strinstr-sparc.s strmake-sparc.s strmov-sparc.s \ strnmov-sparc.s strstr-sparc.s strxmov-sparc.s \ t_ctype.h my_strchr.c CMakeLists.txt \ - CHARSET_INFO.txt + CHARSET_INFO.txt strmov_overlapp.c libmystrings_a_LIBADD= conf_to_src_SOURCES = conf_to_src.c xml.c ctype.c bcmp.c diff --git a/strings/strmov_overlapp.c b/strings/strmov_overlapp.c new file mode 100644 index 00000000000..4cc3e294620 --- /dev/null +++ b/strings/strmov_overlapp.c @@ -0,0 +1,26 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include "m_string.h" + +/* A trivial implementation */ +char *strmov_overlapp(char *dst, const char *src) +{ + size_t len= strlen(src); + memmove(dst, src, len+1); + return dst+len; +} + From fd485ad9889f2d8db6a2de6c61efb720cb97e96a Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Wed, 10 Jun 2009 01:11:33 +0400 Subject: [PATCH 04/27] MWL#17: Table elimination - Make elimination work with aggregate functions. The problem was that aggregate functions reported all table bits in used_tables(), and that prevented table elimination. Fixed by making aggregate functions return more correct value from used_tables(). mysql-test/r/ps_11bugs.result: MWL#17: Table elimination - Update test results. The difference is because of Item_ref change: outer references to constants are now recognized as constants, too. mysql-test/r/subselect.result: - Update test results. The difference is because of Item_ref change: outer references to constants are now recognized as constants, too. mysql-test/r/table_elim.result: MWL#17: Table elimination - Check that elimination works in presense of aggreagate functions mysql-test/t/table_elim.test: MWL#17: Table elimination - Check that elimination works in presense of aggreagate functions sql/item.h: MWL#17: Table elimination - Add Item_ref::const_item() which calls (*ref)->const_item(). Before this diff Item_ref used the default implementation of const_item(){ return used_tables()==0; }. This is no longer true, as COUNT(*) now has used_tables()==0 but const_item()==FALSE. sql/item_sum.cc: MWL#17: Table elimination - Make Item_sum() and it descendants not to return all bits in used_tables(). This is needed because otherwise table elimination can't work in presense of aggregate functions - COUNT(*) now has used_tables()==0 and const_item()==FALSE. Had to change Item_ref::const_item() to account for this. sql/item_sum.h: MWL#17: Table elimination - Add comments --- mysql-test/r/ps_11bugs.result | 4 ++-- mysql-test/r/subselect.result | 4 ++-- mysql-test/r/table_elim.result | 15 +++++++++++++++ mysql-test/t/table_elim.test | 9 +++++++-- sql/item.h | 4 ++++ sql/item_sum.cc | 5 ----- sql/item_sum.h | 14 ++++++++++++++ 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/mysql-test/r/ps_11bugs.result b/mysql-test/r/ps_11bugs.result index a298c552806..5c11163ab9e 100644 --- a/mysql-test/r/ps_11bugs.result +++ b/mysql-test/r/ps_11bugs.result @@ -121,8 +121,8 @@ insert into t1 values (1); explain select * from t1 where 3 in (select (1+1) union select 1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used -3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible HAVING +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL Impossible HAVING NULL UNION RESULT ALL NULL NULL NULL NULL NULL select * from t1 where 3 in (select (1+1) union select 1); a diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result index 5fb1425f10a..ac15287fb30 100644 --- a/mysql-test/r/subselect.result +++ b/mysql-test/r/subselect.result @@ -4353,13 +4353,13 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 2 100.00 2 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort Warnings: -Note 1003 select 1 AS `1` from `test`.`t1` where (1,(select 1 AS `1` from `test`.`t1` group by `test`.`t1`.`a` having ((1) = (1)))) +Note 1003 select 1 AS `1` from `test`.`t1` where (1,(select 1 AS `1` from `test`.`t1` group by `test`.`t1`.`a` having 1)) EXPLAIN EXTENDED SELECT 1 FROM t1 WHERE 1 IN (SELECT 1 FROM t1 WHERE a > 3 GROUP BY a); id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 2 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using temporary; Using filesort Warnings: -Note 1003 select 1 AS `1` from `test`.`t1` where (1,(select 1 AS `1` from `test`.`t1` where (`test`.`t1`.`a` > 3) group by `test`.`t1`.`a` having ((1) = (1)))) +Note 1003 select 1 AS `1` from `test`.`t1` where (1,(select 1 AS `1` from `test`.`t1` where (`test`.`t1`.`a` > 3) group by `test`.`t1`.`a` having 1)) DROP TABLE t1; End of 5.0 tests. CREATE TABLE t1 (a INT, b INT); diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result index c424c5824a4..0c0672e890b 100644 --- a/mysql-test/r/table_elim.result +++ b/mysql-test/r/table_elim.result @@ -53,4 +53,19 @@ t3.a=t1.a) on t0.a=t1.a; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 4 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +# Elimination with aggregate functions +explain select count(*) from t1 left join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +explain select count(1) from t1 left join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +explain select count(1) from t1 left join t2 on t2.a=t1.a group by t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +This must not use elimination: +explain select count(1) from t1 left join t2 on t2.a=t1.a group by t2.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 Using index drop table t0, t1, t2, t3; diff --git a/mysql-test/t/table_elim.test b/mysql-test/t/table_elim.test index c540db884ea..4c8c360ea04 100644 --- a/mysql-test/t/table_elim.test +++ b/mysql-test/t/table_elim.test @@ -29,8 +29,6 @@ explain select t1.a from t1 left join t2 on t2.a=t1.a order by t2.b; --echo # This will not be eliminated as t2.b is in group list: explain select t1.a from t1 left join t2 on t2.a=t1.a group by t2.b; -## TODO: Aggregate functions prevent table elimination ATM. - --echo # This will not be eliminated as t2.b is in the WHERE explain select t1.a from t1 left join t2 on t2.a=t1.a where t2.b < 3 or t2.b is null; @@ -47,6 +45,13 @@ from t0 left join (t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a) on t0.a=t1.a; +--echo # Elimination with aggregate functions +explain select count(*) from t1 left join t2 on t2.a=t1.a; +explain select count(1) from t1 left join t2 on t2.a=t1.a; +explain select count(1) from t1 left join t2 on t2.a=t1.a group by t1.a; + +--echo This must not use elimination: +explain select count(1) from t1 left join t2 on t2.a=t1.a group by t2.a; drop table t0, t1, t2, t3; diff --git a/sql/item.h b/sql/item.h index 9daf353998d..4b609d58776 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2203,6 +2203,10 @@ public: if (!depended_from) (*ref)->update_used_tables(); } + bool const_item() const + { + return (*ref)->const_item(); + } table_map not_null_tables() const { return (*ref)->not_null_tables(); } void set_result_field(Field *field) { result_field= field; } bool is_result_field() { return 1; } diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 21501d9becf..0332141971e 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -542,11 +542,6 @@ void Item_sum::update_used_tables () args[i]->update_used_tables(); used_tables_cache|= args[i]->used_tables(); } - - used_tables_cache&= PSEUDO_TABLE_BITS; - - /* the aggregate function is aggregated into its local context */ - used_tables_cache |= (1 << aggr_sel->join->tables) - 1; } } diff --git a/sql/item_sum.h b/sql/item_sum.h index d991327d847..aec5830f381 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -255,6 +255,12 @@ protected: */ Item **orig_args, *tmp_orig_args[2]; table_map used_tables_cache; + + /* + TRUE <=> We've managed to calculate the value of this Item in + opt_sum_query(), hence it can be considered constant at all subsequent + steps. + */ bool forced_const; public: @@ -341,6 +347,14 @@ public: virtual const char *func_name() const= 0; virtual Item *result_item(Field *field) { return new Item_field(field); } + /* + Return bitmap of tables that are needed to evaluate the item. + + The implementation takes into account the used strategy: items resolved + at optimization phase report 0. + Items that depend on the number of rows only, e.g. COUNT(*) will report + zero, but will still false from const_item(). + */ table_map used_tables() const { return used_tables_cache; } void update_used_tables (); void cleanup() From 402e58cf788af1defe3e58cacedbdd77f5dffcf0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Sun, 14 Jun 2009 14:01:10 +0400 Subject: [PATCH 05/27] MWL#17: Table elimination - Do not show eliminated tables in the output of EXPLAIN EXTENDED --- mysql-test/r/table_elim.result | 15 +++++++++++---- mysql-test/t/table_elim.test | 3 ++- sql/sql_select.cc | 35 +++++++++++++++++++++++++++------- sql/sql_select.h | 5 ++--- sql/table.h | 3 ++- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result index 0c0672e890b..8ff8786a574 100644 --- a/mysql-test/r/table_elim.result +++ b/mysql-test/r/table_elim.result @@ -10,6 +10,11 @@ as select a, a as b from t1 where a in (1,3); explain select t1.a from t1 left join t2 on t2.a=t1.a; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +explain extended select t1.a from t1 left join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where 1 select t1.a from t1 left join t2 on t2.a=t1.a; a 0 @@ -45,14 +50,16 @@ explain select t1.a from t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 # Elimination when done within an outer join nest: -explain +explain extended select t0.* from t0 left join (t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a) on t0.a=t1.a; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t0 ALL NULL NULL NULL NULL 4 -1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 4 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 +Warnings: +Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t0` left join (`test`.`t1`) on((`test`.`t0`.`a` = `test`.`t1`.`a`)) where 1 # Elimination with aggregate functions explain select count(*) from t1 left join t2 on t2.a=t1.a; id select_type table type possible_keys key key_len ref rows Extra diff --git a/mysql-test/t/table_elim.test b/mysql-test/t/table_elim.test index 4c8c360ea04..0e4225f8c9a 100644 --- a/mysql-test/t/table_elim.test +++ b/mysql-test/t/table_elim.test @@ -17,6 +17,7 @@ create table t3 (a int primary key, b int) --echo # This will be eliminated: explain select t1.a from t1 left join t2 on t2.a=t1.a; +explain extended select t1.a from t1 left join t2 on t2.a=t1.a; select t1.a from t1 left join t2 on t2.a=t1.a; @@ -39,7 +40,7 @@ explain select t1.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a; explain select t1.a from t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; --echo # Elimination when done within an outer join nest: -explain +explain extended select t0.* from t0 left join (t1 left join (t2 join t3 on t2.b=t3.b) on t2.a=t1.a and diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7a2fb857504..6041cd6edfb 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2386,6 +2386,10 @@ mysql_select(THD *thd, Item ***rref_pointer_array, } else { + // psergey{ + if (select_options & SELECT_DESCRIBE) + free_join= 0; + // }psergey if (!(join= new JOIN(thd, fields, select_options, result))) DBUG_RETURN(TRUE); thd_proc_info(thd, "init"); @@ -2523,7 +2527,7 @@ static void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_c { DBUG_PRINT("info", ("Eliminated table %s", table->alias)); tab->type= JT_CONST; - tab->eliminated= TRUE; + join->eliminated_tables |= table->map; *const_tables |= table->map; join->const_table_map|= table->map; set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); @@ -2726,6 +2730,10 @@ static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const Item *item; table_map used_tables; DBUG_ENTER("eliminate_tables"); + + join->eliminated_tables= 0; + + /* MWL#17 is only about outer join elimination, so: */ if (!join->outer_join) DBUG_VOID_RETURN; @@ -6060,6 +6068,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *tmp_table) tables= 1; const_tables= 0; const_table_map= 0; + eliminated_tables= 0; tmp_table_param.field_count= tmp_table_param.sum_func_count= tmp_table_param.func_count= 0; tmp_table_param.copy_field= tmp_table_param.copy_field_end=0; @@ -16509,7 +16518,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, quick_type= -1; //psergey-todo: - if (tab->eliminated) + if (table->map & join->eliminated_tables) { used_tables|=table->map; continue; @@ -16912,6 +16921,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) */ static void print_join(THD *thd, + table_map eliminated_tables, String *str, List *tables, enum_query_type query_type) @@ -16927,12 +16937,22 @@ static void print_join(THD *thd, *t= ti++; DBUG_ASSERT(tables->elements >= 1); - (*table)->print(thd, str, query_type); + //pserey:TODO check! + (*table)->print(thd, eliminated_tables, str, query_type); TABLE_LIST **end= table + tables->elements; for (TABLE_LIST **tbl= table + 1; tbl < end; tbl++) { TABLE_LIST *curr= *tbl; + // psergey-todo-todo: + // base table: check + if (curr->table && (curr->table->map & eliminated_tables) || + curr->nested_join && !(curr->nested_join->used_tables & + ~eliminated_tables)) + { + continue; + } + if (curr->outer_join) { /* MySQL converts right to left joins */ @@ -16942,7 +16962,7 @@ static void print_join(THD *thd, str->append(STRING_WITH_LEN(" straight_join ")); else str->append(STRING_WITH_LEN(" join ")); - curr->print(thd, str, query_type); + curr->print(thd, eliminated_tables, str, query_type); if (curr->on_expr) { str->append(STRING_WITH_LEN(" on(")); @@ -16996,12 +17016,13 @@ Index_hint::print(THD *thd, String *str) @param str string where table should be printed */ -void TABLE_LIST::print(THD *thd, String *str, enum_query_type query_type) +void TABLE_LIST::print(THD *thd, table_map eliminated_tables, String *str, + enum_query_type query_type) { if (nested_join) { str->append('('); - print_join(thd, str, &nested_join->join_list, query_type); + print_join(thd, eliminated_tables, str, &nested_join->join_list, query_type); str->append(')'); } else @@ -17143,7 +17164,7 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) { str->append(STRING_WITH_LEN(" from ")); /* go through join tree */ - print_join(thd, str, &top_join_list, query_type); + print_join(thd, join->eliminated_tables, str, &top_join_list, query_type); } else if (where) { diff --git a/sql/sql_select.h b/sql/sql_select.h index d0c16558c91..0ca47e7ce13 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -211,9 +211,6 @@ typedef struct st_join_table { /** Bitmap of nested joins this table is part of */ nested_join_map embedding_map; - //psergey-todo: more justified place - bool eliminated; - void cleanup(); inline bool is_using_loose_index_scan() { @@ -289,6 +286,8 @@ public: */ bool resume_nested_loop; table_map const_table_map,found_const_table_map; + + table_map eliminated_tables; /* Bitmap of all inner tables from outer joins */ diff --git a/sql/table.h b/sql/table.h index cc54daccf9d..f443d591dec 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1366,7 +1366,8 @@ struct TABLE_LIST return (derived || view || schema_table || (create && !table->db_stat) || !table); } - void print(THD *thd, String *str, enum_query_type query_type); + void print(THD *thd, table_map eliminated_tables, String *str, + enum_query_type query_type); bool check_single_table(TABLE_LIST **table, table_map map, TABLE_LIST *view); bool set_insert_values(MEM_ROOT *mem_root); From b1e25edc9b632224d699a4ba0dba37e9c485b203 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Sun, 14 Jun 2009 16:35:04 +0400 Subject: [PATCH 06/27] MWL#17: Table elimination - Fix the previous cset: take into account that select_lex may be printed when 1. There is no select_lex->join at all (in that case, assume that no tables were eliminated) 2. select_lex->join exists but there was no JOIN::optimize() call yet. handle this by initializing join->eliminated really early. --- sql/sql_select.cc | 6 +++--- sql/sql_select.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6041cd6edfb..56fb9ac47ba 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2731,7 +2731,7 @@ static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const table_map used_tables; DBUG_ENTER("eliminate_tables"); - join->eliminated_tables= 0; + DBUG_ASSERT(join->eliminated_tables == 0); /* MWL#17 is only about outer join elimination, so: */ if (!join->outer_join) @@ -16888,7 +16888,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) unit->fake_select_lex->options|= SELECT_DESCRIBE; if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) res= unit->exec(); - res|= unit->cleanup(); + //psergey-move: res|= unit->cleanup(); } else { @@ -17164,7 +17164,7 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) { str->append(STRING_WITH_LEN(" from ")); /* go through join tree */ - print_join(thd, join->eliminated_tables, str, &top_join_list, query_type); + print_join(thd, join? join->eliminated_tables: 0, str, &top_join_list, query_type); } else if (where) { diff --git a/sql/sql_select.h b/sql/sql_select.h index 0ca47e7ce13..f209cee4956 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -427,6 +427,7 @@ public: table= 0; tables= 0; const_tables= 0; + eliminated_tables= 0; join_list= 0; sort_and_group= 0; first_record= 0; From 6b4727dcb9d3451cbb5c3457e0c07c81f1c503f0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Mon, 15 Jun 2009 00:59:24 +0400 Subject: [PATCH 07/27] MWL#17: Table elimination - Fix print_join() to work both for EXPLAIN EXTENDED (after table elimination) and for CREATE VIEW (after join->prepare() but without any optimization). mysql-test/r/union.result: MWL#17: Table elimination - Adjust test results --- mysql-test/r/union.result | 2 +- sql/sql_select.cc | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index da89c7ce386..7111bd41748 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -522,7 +522,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 2 UNION t2 const PRIMARY PRIMARY 4 const 1 100.00 NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: -Note 1003 (select '1' AS `a`,'1' AS `b` from `test`.`t1` where ('1' = 1)) union (select '1' AS `a`,'10' AS `b` from `test`.`t2` where ('1' = 1)) +Note 1003 (select '1' AS `a`,'1' AS `b` from `test`.`t1` where 1) union (select '1' AS `a`,'10' AS `b` from `test`.`t2` where 1) (select * from t1 where a=5) union (select * from t2 where a=1); a b 1 10 diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 56fb9ac47ba..e8186a7da36 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -16937,18 +16937,28 @@ static void print_join(THD *thd, *t= ti++; DBUG_ASSERT(tables->elements >= 1); - //pserey:TODO check! + /* + Assert that the first table in the list isn't eliminated (if it was we + would have skipped the entire join nest) + */ + DBUG_ASSERT(!eliminated_tables || + !((*table)->table && ((*table)->table->map & eliminated_tables) || + (*table)->nested_join && !((*table)->nested_join->used_tables & + ~eliminated_tables))); (*table)->print(thd, eliminated_tables, str, query_type); TABLE_LIST **end= table + tables->elements; for (TABLE_LIST **tbl= table + 1; tbl < end; tbl++) { TABLE_LIST *curr= *tbl; - // psergey-todo-todo: - // base table: check - if (curr->table && (curr->table->map & eliminated_tables) || - curr->nested_join && !(curr->nested_join->used_tables & - ~eliminated_tables)) + /* + The (*) check guards againist the case of printing the query for + CREATE VIEW. There we'll have nested_join->used_tables==0. + */ + if (eliminated_tables && // (*) + (curr->table && (curr->table->map & eliminated_tables) || + curr->nested_join && !(curr->nested_join->used_tables & + ~eliminated_tables))) { continue; } From a0a7cb03ae23d4962c3b8f710ebed4b0764efb56 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Mon, 15 Jun 2009 20:22:08 +0400 Subject: [PATCH 08/27] Fix spurious valgrind warnings in rpl_trigger.test --- mysql-test/valgrind.supp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mysql-test/valgrind.supp b/mysql-test/valgrind.supp index 2d90213b112..bcc4991d4c5 100644 --- a/mysql-test/valgrind.supp +++ b/mysql-test/valgrind.supp @@ -631,3 +631,13 @@ fun:malloc fun:inet_ntoa } + +# +# Some problem inside glibc on Ubuntu 9.04, x86 (but not amd64) +# +{ + Mem loss inside nptl_pthread_exit_hack_handler + Memcheck:Leak + ... + fun:nptl_pthread_exit_hack_handler +} From d8640e3eaebe4007eab9447e5af124534e046515 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Tue, 16 Jun 2009 23:54:13 +0400 Subject: [PATCH 09/27] MWL#17: Table elimination - Move eliminate_tables() to before constant table detection. - First code for benchmark sql-bench/test-table-elimination.sh: MWL#17: Table elimination - sql-bench "Benchmark", incomplete sql/sql_select.cc: MWL#17: Table elimination - Move eliminate_tables() to before constant table detection, this will allow to spare const table reads (at a cost of not being able to take advantage of tables that are constant because they have no records, but this case is of lesser importance) --- sql-bench/test-table-elimination.sh | 320 ++++++++++++++++++++++++++++ sql/sql_select.cc | 29 ++- 2 files changed, 338 insertions(+), 11 deletions(-) create mode 100755 sql-bench/test-table-elimination.sh diff --git a/sql-bench/test-table-elimination.sh b/sql-bench/test-table-elimination.sh new file mode 100755 index 00000000000..b2464218531 --- /dev/null +++ b/sql-bench/test-table-elimination.sh @@ -0,0 +1,320 @@ +#!@PERL@ +# Test of table elimination feature + +use Cwd; +use DBI; +use Getopt::Long; +use Benchmark; + +$opt_loop_count=100000; +$opt_medium_loop_count=10000; +$opt_small_loop_count=100; + +$pwd = cwd(); $pwd = "." if ($pwd eq ''); +require "$pwd/bench-init.pl" || die "Can't read Configuration file: $!\n"; + +if ($opt_small_test) +{ + $opt_loop_count/=10; + $opt_medium_loop_count/=10; + $opt_small_loop_count/=10; +} + +print "Testing table elimination feature\n"; +print "The test table has $opt_loop_count rows.\n\n"; + +# A query to get the recent versions of all attributes: +$select_current_full_facts=" + select + F.id, A1.attr1, A2.attr2 + from + elim_facts F + left join elim_attr1 A1 on A1.id=F.id + left join elim_attr2 A2 on A2.id=F.id and + A2.fromdate=(select MAX(fromdate) from + elim_attr2 where id=A2.id); +"; +$select_current_full_facts=" + select + F.id, A1.attr1, A2.attr2 + from + elim_facts F + left join elim_attr1 A1 on A1.id=F.id + left join elim_attr2 A2 on A2.id=F.id and + A2.fromdate=(select MAX(fromdate) from + elim_attr2 where id=F.id); +"; +# TODO: same as above but for some given date also? +# TODO: + + +#### +#### Connect and start timeing +#### + +$dbh = $server->connect(); +$start_time=new Benchmark; + +#### +#### Create needed tables +#### + +goto select_test if ($opt_skip_create); + +print "Creating tables\n"; +$dbh->do("drop table elim_facts" . $server->{'drop_attr'}); +$dbh->do("drop table elim_attr1" . $server->{'drop_attr'}); +$dbh->do("drop table elim_attr2" . $server->{'drop_attr'}); + +# The facts table +do_many($dbh,$server->create("elim_facts", + ["id integer"], + ["primary key (id)"])); + +# Attribute1, non-versioned +do_many($dbh,$server->create("elim_attr1", + ["id integer", + "attr1 integer"], + ["primary key (id)", + "key (attr1)"])); + +# Attribute1, time-versioned +do_many($dbh,$server->create("elim_attr2", + ["id integer", + "attr2 integer", + "fromdate date"], + ["primary key (id, fromdate)", + "key (attr2,fromdate)"])); + +#NOTE: ignoring: if ($limits->{'views'}) +$dbh->do("drop view elim_current_facts"); +$dbh->do("create view elim_current_facts as $select_current_full_facts"); + +if ($opt_lock_tables) +{ + do_query($dbh,"LOCK TABLES elim_facts, elim_attr1, elim_attr2 WRITE"); +} + +if ($opt_fast && defined($server->{vacuum})) +{ + $server->vacuum(1,\$dbh); +} + +#### +#### Fill the facts table +#### +$n_facts= $opt_loop_count; + +if ($opt_fast && $server->{transactions}) +{ + $dbh->{AutoCommit} = 0; +} + +print "Inserting $n_facts rows into facts table\n"; +$loop_time=new Benchmark; + +$query="insert into elim_facts values ("; +for ($id=0; $id < $n_facts ; $id++) +{ + do_query($dbh,"$query $id)"); +} + +if ($opt_fast && $server->{transactions}) +{ + $dbh->commit; + $dbh->{AutoCommit} = 1; +} + +$end_time=new Benchmark; +print "Time to insert ($n_facts): " . + timestr(timediff($end_time, $loop_time),"all") . "\n\n"; + +#### +#### Fill attr1 table +#### +if ($opt_fast && $server->{transactions}) +{ + $dbh->{AutoCommit} = 0; +} + +print "Inserting $n_facts rows into attr1 table\n"; +$loop_time=new Benchmark; + +$query="insert into elim_attr1 values ("; +for ($id=0; $id < $n_facts ; $id++) +{ + $attr1= ceil(rand($n_facts)); + do_query($dbh,"$query $id, $attr1)"); +} + +if ($opt_fast && $server->{transactions}) +{ + $dbh->commit; + $dbh->{AutoCommit} = 1; +} + +$end_time=new Benchmark; +print "Time to insert ($n_facts): " . + timestr(timediff($end_time, $loop_time),"all") . "\n\n"; + +#### +#### Fill attr2 table +#### +if ($opt_fast && $server->{transactions}) +{ + $dbh->{AutoCommit} = 0; +} + +print "Inserting $n_facts rows into attr2 table\n"; +$loop_time=new Benchmark; + +for ($id=0; $id < $n_facts ; $id++) +{ + # Two values for each $id - current one and obsolete one. + $attr1= ceil(rand($n_facts)); + $query="insert into elim_attr2 values ($id, $attr1, now())"; + do_query($dbh,$query); + $query="insert into elim_attr2 values ($id, $attr1, '2009-01-01')"; + do_query($dbh,$query); +} + +if ($opt_fast && $server->{transactions}) +{ + $dbh->commit; + $dbh->{AutoCommit} = 1; +} + +$end_time=new Benchmark; +print "Time to insert ($n_facts): " . + timestr(timediff($end_time, $loop_time),"all") . "\n\n"; + +#### +#### Finalize the database population +#### + +if ($opt_lock_tables) +{ + do_query($dbh,"UNLOCK TABLES"); +} + +if ($opt_fast && defined($server->{vacuum})) +{ + $server->vacuum(0,\$dbh,["elim_facts", "elim_attr1", "elim_attr2"]); +} + +if ($opt_lock_tables) +{ + do_query($dbh,"LOCK TABLES elim_facts, elim_attr1, elim_attr2 WRITE"); +} + +#### +#### Do some selects on the table +#### + +select_test: + +# +# The selects will be: +# - N pk-lookups with all attributes +# - pk-attribute-based lookup +# - latest-attribute value based lookup. + + +### +### Bare facts select: +### +print "testing bare facts facts table\n"; +$loop_time=new Benchmark; +$rows=0; +for ($i=0 ; $i < $opt_medium_loop_count ; $i++) +{ + $val= ceil(rand($n_facts)); + $rows+=fetch_all_rows($dbh,"select * from elim_facts where id=$val"); +} +$count=$i; + +$end_time=new Benchmark; +print "time for select_bare_facts ($count:$rows): " . + timestr(timediff($end_time, $loop_time),"all") . "\n"; + + +### +### Full facts select, no elimination: +### +print "testing full facts facts table\n"; +$loop_time=new Benchmark; +$rows=0; +for ($i=0 ; $i < $opt_medium_loop_count ; $i++) +{ + $val= rand($n_facts); + $rows+=fetch_all_rows($dbh,"select * from elim_current_facts where id=$val"); +} +$count=$i; + +$end_time=new Benchmark; +print "time for select_two_attributes ($count:$rows): " . + timestr(timediff($end_time, $loop_time),"all") . "\n"; + +### +### Now with elimination: select only only one fact +### +print "testing selection of one attribute\n"; +$loop_time=new Benchmark; +$rows=0; +for ($i=0 ; $i < $opt_medium_loop_count ; $i++) +{ + $val= rand($n_facts); + $rows+=fetch_all_rows($dbh,"select id, attr1 from elim_current_facts where id=$val"); +} +$count=$i; + +$end_time=new Benchmark; +print "time for select_one_attribute ($count:$rows): " . + timestr(timediff($end_time, $loop_time),"all") . "\n"; + +### +### Now with elimination: select only only one fact +### +print "testing selection of one attribute\n"; +$loop_time=new Benchmark; +$rows=0; +for ($i=0 ; $i < $opt_medium_loop_count ; $i++) +{ + $val= rand($n_facts); + $rows+=fetch_all_rows($dbh,"select id, attr2 from elim_current_facts where id=$val"); +} +$count=$i; + +$end_time=new Benchmark; +print "time for select_one_attribute ($count:$rows): " . + timestr(timediff($end_time, $loop_time),"all") . "\n"; + + +### +### TODO... +### + +; + +#### +#### End of benchmark +#### + +if ($opt_lock_tables) +{ + do_query($dbh,"UNLOCK TABLES"); +} +if (!$opt_skip_delete) +{ + do_query($dbh,"drop table elim_facts, elim_attr1, elim_attr2" . $server->{'drop_attr'}); +} + +if ($opt_fast && defined($server->{vacuum})) +{ + $server->vacuum(0,\$dbh); +} + +$dbh->disconnect; # close connection + +end_benchmark($start_time); + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e8186a7da36..b460ee05ae8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2959,22 +2959,28 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, /* Read tables with 0 or 1 rows (system tables) */ join->const_table_map= 0; + + eliminate_tables(join, &const_count, &found_const_table_map); + join->const_table_map= found_const_table_map; for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count; p_pos < p_end ; p_pos++) { - int tmp; s= p_pos->table; - s->type=JT_SYSTEM; - join->const_table_map|=s->table->map; - if ((tmp=join_read_const_table(s, p_pos))) + if (! (s->table->map & join->eliminated_tables)) { - if (tmp > 0) - goto error; // Fatal error + int tmp; + s->type=JT_SYSTEM; + join->const_table_map|=s->table->map; + if ((tmp=join_read_const_table(s, p_pos))) + { + if (tmp > 0) + goto error; // Fatal error + } + else + found_const_table_map|= s->table->map; } - else - found_const_table_map|= s->table->map; } /* loop until no more const tables are found */ @@ -2999,7 +3005,8 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, substitution of a const table the key value happens to be null then we can state that there are no matches for this equi-join. */ - if ((keyuse= s->keyuse) && *s->on_expr_ref && !s->embedding_map) + if ((keyuse= s->keyuse) && *s->on_expr_ref && !s->embedding_map && + !(table->map & join->eliminated_tables)) { /* When performing an outer join operation if there are no matching rows @@ -3135,7 +3142,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, } //psergey-todo: table elimination - eliminate_tables(join, &const_count, &found_const_table_map); + //eliminate_tables(join, &const_count, &found_const_table_map); //:psergey-todo /* Calc how many (possible) matched records in each table */ @@ -16517,7 +16524,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, quick_type= -1; - //psergey-todo: + /* Don't show eliminated tables */ if (table->map & join->eliminated_tables) { used_tables|=table->map; From 7a9f45c6d5fc90a18c44e52ddd147e95fdacc264 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Wed, 17 Jun 2009 09:27:39 +0400 Subject: [PATCH 10/27] * Use excessive parentheses to stop compiler warning * Fix test results to account for changes in previous cset --- mysql-test/r/select.result | 1 - sql/sql_select.cc | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/select.result b/mysql-test/r/select.result index 0771c7fb370..1dcc1cb54d1 100644 --- a/mysql-test/r/select.result +++ b/mysql-test/r/select.result @@ -3585,7 +3585,6 @@ INSERT INTO t2 VALUES (1,'a'),(2,'b'),(3,'c'); EXPLAIN SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.b=t1.b WHERE t1.a=3; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 const PRIMARY PRIMARY 4 const 1 -1 SIMPLE t2 const b b 22 const 1 Using index DROP TABLE t1,t2; CREATE TABLE t1(id int PRIMARY KEY, b int, e int); CREATE TABLE t2(i int, a int, INDEX si(i), INDEX ai(a)); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b460ee05ae8..dbc300adae0 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -16963,9 +16963,9 @@ static void print_join(THD *thd, CREATE VIEW. There we'll have nested_join->used_tables==0. */ if (eliminated_tables && // (*) - (curr->table && (curr->table->map & eliminated_tables) || - curr->nested_join && !(curr->nested_join->used_tables & - ~eliminated_tables))) + ((curr->table && (curr->table->map & eliminated_tables)) || + (curr->nested_join && !(curr->nested_join->used_tables & + ~eliminated_tables)))) { continue; } From 9e65634b2337e96445e14ce72b144353f779a4a5 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Mon, 22 Jun 2009 15:46:31 +0400 Subject: [PATCH 11/27] MWL#17: Table elimination - Make elimination check to be able detect cases like t.primary_key_col1=othertbl.col AND t.primary_key_col2=func(t.primary_key_col1). These are needed to handle e.g. the case of func() being a correlated subquery that selects the latest value. - If we've removed a condition with subquery predicate, EXPLAIN [EXTENDED] won't show the subquery anymore sql/item.cc: MWL#17: Table elimination - Add tem_field::check_column_usage_processor(). it allows to check which key parts a condition depends on. sql/item.h: MWL#17: Table elimination - Add tem_field::check_column_usage_processor(). it allows to check which key parts a condition depends on. sql/item_subselect.cc: MWL#17: Table elimination - Item_subselect got 'eliminated' attribute. It is used only to determine if the subselect should be printed by EXPLAIN. - Item_subselect got List refers_to - a list of item in the current select that are referred to from within the subselect. - Added Item_*::check_column_usage_processor(). it allows to check which key parts a condition depends on. - Added a comment about possible problem in Item_subselect::walk sql/item_subselect.h: MWL#17: Table elimination - Item_subselect got 'eliminated' attribute. It is used only to determine if the subselect should be printed by EXPLAIN. - Item_subselect got List refers_to - a list of item in the current select that are referred to from within the subselect. - Added Item_*::check_column_usage_processor(). it allows to check which key parts a condition depends on. sql/item_sum.cc: MWL#17: Table elimination sql/sql_lex.cc: MWL#17: Table elimination sql/sql_lex.h: MWL#17: Table elimination sql/sql_select.h: MWL#17: Table elimination --- sql/item.cc | 26 ++++++- sql/item.h | 11 +++ sql/item_subselect.cc | 30 ++++++- sql/item_subselect.h | 12 ++- sql/item_sum.cc | 2 +- sql/sql_lex.cc | 4 +- sql/sql_lex.h | 2 +- sql/sql_select.cc | 176 +++++++++++++++++++++++++++++++++++------- sql/sql_select.h | 1 + 9 files changed, 229 insertions(+), 35 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index 010e4cb441a..d317b16a264 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1915,6 +1915,30 @@ void Item_field::reset_field(Field *f) name= (char*) f->field_name; } +bool Item_field::check_column_usage_processor(uchar *arg) +{ + Field_processor_info* info=(Field_processor_info*)arg; + if (used_tables() & ~info->allowed_tables) + return FALSE; + + if (field->table == info->table) + { + if (!(field->part_of_key.is_set(info->keyno))) + return TRUE; + + KEY *key= &field->table->key_info[info->keyno]; + for (uint part= 0; part < key->key_parts; part++) + { + if (field->field_index == key->key_part[part].field->field_index) + { + info->needed_key_parts |= key_part_map(1) << part; + break; + } + } + } + return FALSE; +} + const char *Item_ident::full_name() const { char *tmp; @@ -3380,7 +3404,7 @@ static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, /* store pointer on SELECT_LEX from which item is dependent */ if (mark_item) mark_item->depended_from= last; - current->mark_as_dependent(last); + current->mark_as_dependent(last, resolved_item); if (thd->lex->describe & DESCRIBE_EXTENDED) { char warn_buff[MYSQL_ERRMSG_SIZE]; diff --git a/sql/item.h b/sql/item.h index 4b609d58776..e9fb39443bc 100644 --- a/sql/item.h +++ b/sql/item.h @@ -888,6 +888,8 @@ public: virtual bool reset_query_id_processor(uchar *query_id_arg) { return 0; } virtual bool is_expensive_processor(uchar *arg) { return 0; } virtual bool register_field_in_read_map(uchar *arg) { return 0; } + virtual bool check_column_usage_processor(uchar *arg) { return 0; } + virtual bool mark_as_eliminated_processor(uchar *arg) { return 0; } /* Check if a partition function is allowed SYNOPSIS @@ -1012,6 +1014,14 @@ public: }; +typedef struct +{ + table_map allowed_tables; + TABLE *table; + uint keyno; + uint needed_key_parts; +} Field_processor_info; + class sp_head; @@ -1477,6 +1487,7 @@ public: bool find_item_in_field_list_processor(uchar *arg); bool register_field_in_read_map(uchar *arg); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} + bool check_column_usage_processor(uchar *arg); void cleanup(); bool result_as_longlong() { diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 04d4acc16a3..7559a183e60 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -39,7 +39,7 @@ inline Item * and_items(Item* cond, Item *item) Item_subselect::Item_subselect(): Item_result_field(), value_assigned(0), thd(0), substitution(0), engine(0), old_engine(0), used_tables_cache(0), have_to_be_excluded(0), - const_item_cache(1), engine_changed(0), changed(0), is_correlated(FALSE) + const_item_cache(1), in_fix_fields(0), engine_changed(0), changed(0), is_correlated(FALSE) { with_subselect= 1; reset(); @@ -151,10 +151,14 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) DBUG_ASSERT(fixed == 0); engine->set_thd((thd= thd_param)); + if (!in_fix_fields) + refers_to.empty(); + eliminated= FALSE; if (check_stack_overrun(thd, STACK_MIN_SIZE, (uchar*)&res)) return TRUE; - + + in_fix_fields++; res= engine->prepare(); // all transformation is done (used by prepared statements) @@ -181,12 +185,14 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) if (!(*ref)->fixed) ret= (*ref)->fix_fields(thd, ref); thd->where= save_where; + in_fix_fields--; return ret; } // Is it one field subselect? if (engine->cols() > max_columns) { my_error(ER_OPERAND_COLUMNS, MYF(0), 1); + in_fix_fields--; return TRUE; } fix_length_and_dec(); @@ -203,11 +209,30 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) fixed= 1; err: + in_fix_fields--; thd->where= save_where; return res; } +bool Item_subselect::check_column_usage_processor(uchar *arg) +{ + List_iterator it(refers_to); + Item *item; + while ((item= it++)) + { + if (item->walk(&Item::check_column_usage_processor,FALSE, arg)) + return TRUE; + } + return FALSE; +} + +bool Item_subselect::mark_as_eliminated_processor(uchar *arg) +{ + eliminated= TRUE; + return FALSE; +} + bool Item_subselect::walk(Item_processor processor, bool walk_subquery, uchar *argument) { @@ -225,6 +250,7 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery, if (lex->having && (lex->having)->walk(processor, walk_subquery, argument)) return 1; + /* TODO: why doesn't this walk the OUTER JOINs' ON expressions */ while ((item=li++)) { diff --git a/sql/item_subselect.h b/sql/item_subselect.h index d4aa621c083..dc1703c8f34 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -52,8 +52,16 @@ protected: bool have_to_be_excluded; /* cache of constant state */ bool const_item_cache; - + public: + /* + References from inside the subquery to the select that this predicate is + in. References to parent selects not included. + */ + List refers_to; + int in_fix_fields; + bool eliminated; + /* changed engine indicator */ bool engine_changed; /* subquery is transformed */ @@ -126,6 +134,8 @@ public: virtual void reset_value_registration() {} enum_parsing_place place() { return parsing_place; } bool walk(Item_processor processor, bool walk_subquery, uchar *arg); + bool mark_as_eliminated_processor(uchar *arg); + bool check_column_usage_processor(uchar *arg); /** Get the SELECT_LEX structure associated with this Item. diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 0332141971e..fab64dfbeaf 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -350,7 +350,7 @@ bool Item_sum::register_sum_func(THD *thd, Item **ref) sl= sl->master_unit()->outer_select() ) sl->master_unit()->item->with_sum_func= 1; } - thd->lex->current_select->mark_as_dependent(aggr_sel); + thd->lex->current_select->mark_as_dependent(aggr_sel, NULL); return FALSE; } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index da00ab7a239..dc8dadc971a 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1778,7 +1778,7 @@ void st_select_lex_unit::exclude_tree() 'last' should be reachable from this st_select_lex_node */ -void st_select_lex::mark_as_dependent(st_select_lex *last) +void st_select_lex::mark_as_dependent(st_select_lex *last, Item *dependency) { /* Mark all selects from resolved to 1 before select where was @@ -1804,6 +1804,8 @@ void st_select_lex::mark_as_dependent(st_select_lex *last) } is_correlated= TRUE; this->master_unit()->item->is_correlated= TRUE; + if (dependency) + this->master_unit()->item->refers_to.push_back(dependency); } bool st_select_lex_node::set_braces(bool value) { return 1; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index f34a1c7c36f..69fad07d2e3 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -743,7 +743,7 @@ public: return master_unit()->return_after_parsing(); } - void mark_as_dependent(st_select_lex *last); + void mark_as_dependent(st_select_lex *last, Item *dependency); bool set_braces(bool value); bool inc_in_sum_expr(); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index dbc300adae0..eabf2e36f47 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2478,6 +2478,14 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, } +typedef struct st_keyuse_w_needed_reg +{ + KEYUSE *first; + key_part_map second; + +} Keyuse_w_needed_reg; + +static bool has_eq_ref_access_candidate(TABLE *table, table_map can_refer_to_these) { KEYUSE *keyuse= table->reginfo.join_tab->keyuse; @@ -2494,24 +2502,85 @@ bool has_eq_ref_access_candidate(TABLE *table, table_map can_refer_to_these) { uint key= keyuse->key; key_part_map bound_parts=0; - bool ft_key= test(keyuse->keypart == FT_KEYPART); - + uint n_unusable=0; + bool ft_key= test(keyuse->keypart == FT_KEYPART); + KEY *keyinfo= table->key_info + key; + KEYUSE *key_start = keyuse; + do /* For each keypart and each way to read it */ { - if (!(keyuse->used_tables & ~can_refer_to_these) && - !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) + if (keyuse->usable) { - bound_parts |= keyuse->keypart_map; + if(!(keyuse->used_tables & ~can_refer_to_these) && + !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) + { + bound_parts |= keyuse->keypart_map; + } } + else + n_unusable++; keyuse++; - } while (keyuse->table && keyuse->key == key); + } while (keyuse->table == table && keyuse->key == key); + + if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) + != HA_NOSAME)) + { + continue; + } - KEY *keyinfo= table->key_info + key; - if (!ft_key && - ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME) && - bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) - { + if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) return TRUE; + /* + Ok, usable keyuse elements didn't help us. Try making use of + unusable KEYUSEs (psergey-todo: sane comments:) + */ + if (n_unusable && bound_parts) + { + /* + Check if unusable KEYUSE elements cause all parts of key to be + bound. An unusable keyuse element makes a key part bound when it + represents the following: + + keyXpartY=func(bound_columns, preceding_tables) + + . + */ + Keyuse_w_needed_reg *uses; + if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)*n_unusable))) + return FALSE; + uint n_uses=0; + for (KEYUSE *k= key_start; k!=keyuse; k++) + { + if (!k->usable && !(k->used_tables & ~can_refer_to_these)) + { + //Walk k->val and check which key parts it depends on. + Field_processor_info fp= {can_refer_to_these, table, k->key, 0}; + if (!k->val->walk(&Item::check_column_usage_processor, FALSE, + (uchar*)&fp)) + { + uses[n_uses].first= k; + uses[n_uses].second= fp.needed_key_parts; + n_uses++; + } + } + } + /* Now compute transitive closure */ + uint n_bounded; + do + { + n_bounded= 0; + for (uint i=0; i< n_uses; i++) + { + /* needed_parts is covered by what is already bound*/ + if (!(uses[i].second & ~bound_parts)) + { + bound_parts|= key_part_map(1) << uses[i].first->keypart; + n_bounded++; + } + if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) + return TRUE; + } + } while (n_bounded != 0); } } } @@ -2657,6 +2726,7 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, eliminated += tbl->nested_join->join_list.elements; //psergey-todo: do we need to do anything about removing the join //nest? + tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); } else { @@ -2673,6 +2743,7 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, { mark_table_as_eliminated(join, tbl->table, const_tbl_count, const_tables); + tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); eliminated += 1; } } @@ -3065,14 +3136,16 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, { start_keyuse=keyuse; key=keyuse->key; - s->keys.set_bit(key); // QQ: remove this ? + if (keyuse->usable) + s->keys.set_bit(key); // QQ: remove this ? refs=0; const_ref.clear_all(); eq_part.clear_all(); do { - if (keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) + if (keyuse->usable && keyuse->val->type() != Item::NULL_ITEM && + !keyuse->optimize) { if (!((~found_const_table_map) & keyuse->used_tables)) const_ref.set_bit(keyuse->keypart); @@ -3276,6 +3349,7 @@ typedef struct key_field_t { */ bool null_rejecting; bool *cond_guard; /* See KEYUSE::cond_guard */ + bool usable; } KEY_FIELD; @@ -3284,6 +3358,26 @@ typedef struct key_field_t { This is called for OR between different levels. + That is, the function operates on an array of KEY_FIELD elements which has + two parts: + + $LEFT_PART $RIGHT_PART + +-----------------------+-----------------------+ + start new_fields end + + $LEFT_PART and $RIGHT_PART are arrays that have KEY_FIELD elements for two + parts of the OR condition. Our task is to produce an array of KEY_FIELD + elements that would correspond to "$LEFT_PART OR $RIGHT_PART". + + The rules for combining elements are as follows: + (keyfieldA1 AND keyfieldA2 AND ...) OR (keyfieldB1 AND keyfieldB2 AND ...)= + AND_ij (keyfieldA_i OR keyfieldB_j) + + We discard all (keyfieldA_i OR keyfieldB_j) that refer to different + fields. For those referring to the same field, the logic is as follows: + + t.keycol= + To be able to do 'ref_or_null' we merge a comparison of a column and 'column IS NULL' to one test. This is useful for sub select queries that are internally transformed to something like:. @@ -3348,13 +3442,18 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_REF_OR_NULL)); old->null_rejecting= (old->null_rejecting && new_fields->null_rejecting); + /* + The conditions are the same, hence their usabilities should + be, too (TODO: shouldn't that apply to the above + null_rejecting and optimize attributes?) + */ + DBUG_ASSERT(old->usable == new_fields->usable); } } else if (old->eq_func && new_fields->eq_func && old->val->eq_by_collation(new_fields->val, old->field->binary(), old->field->charset())) - { old->level= and_level; old->optimize= ((old->optimize & new_fields->optimize & @@ -3363,10 +3462,14 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_REF_OR_NULL)); old->null_rejecting= (old->null_rejecting && new_fields->null_rejecting); + // "t.key_col=const" predicates are always usable + DBUG_ASSERT(old->usable && new_fields->usable); } else if (old->eq_func && new_fields->eq_func && - ((old->val->const_item() && old->val->is_null()) || - new_fields->val->is_null())) + ((new_fields->usable && old->val->const_item() && + old->val->is_null()) || + ((old->usable && new_fields->val->is_null())))) + /* TODO ^ why is the above asymmetric, why const_item()? */ { /* field = expression OR field IS NULL */ old->level= and_level; @@ -3437,6 +3540,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, table_map usable_tables, SARGABLE_PARAM **sargables) { uint exists_optimize= 0; + bool optimizable=0; if (!(field->flags & PART_KEY_FLAG)) { // Don't remove column IS NULL on a LEFT JOIN table @@ -3449,15 +3553,15 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, else { table_map used_tables=0; - bool optimizable=0; for (uint i=0; iused_tables(); if (!((value[i])->used_tables() & (field->table->map | RAND_TABLE_BIT))) optimizable=1; } - if (!optimizable) - return; + // psergey-tbl-elim: + // if (!optimizable) + // return; if (!(usable_tables & field->table->map)) { if (!eq_func || (*value)->type() != Item::NULL_ITEM || @@ -3470,7 +3574,8 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, JOIN_TAB *stat=field->table->reginfo.join_tab; key_map possible_keys=field->key_start; possible_keys.intersect(field->table->keys_in_use_for_query); - stat[0].keys.merge(possible_keys); // Add possible keys + if (optimizable) + stat[0].keys.merge(possible_keys); // Add possible keys /* Save the following cases: @@ -3563,6 +3668,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, (*key_fields)->val= *value; (*key_fields)->level= and_level; (*key_fields)->optimize= exists_optimize; + (*key_fields)->usable= optimizable; /* If the condition has form "tbl.keypart = othertbl.field" and othertbl.field can be NULL, there will be no matches if othertbl.field @@ -3874,6 +3980,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.null_rejecting= key_field->null_rejecting; keyuse.cond_guard= key_field->cond_guard; + keyuse.usable= key_field->usable; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } } @@ -3954,6 +4061,11 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) return (int) (a->key - b->key); if (a->keypart != b->keypart) return (int) (a->keypart - b->keypart); + + // Usable ones go before the unusable + if (a->usable != b->usable) + return (int)a->usable - (int)b->usable; + // Place const values before other ones if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - test((b->used_tables & ~OUTER_REF_TABLE_BIT)))) @@ -4164,7 +4276,8 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, found_eq_constant=0; for (i=0 ; i < keyuse->elements-1 ; i++,use++) { - if (!use->used_tables && use->optimize != KEY_OPTIMIZE_REF_OR_NULL) + if (use->usable && !use->used_tables && + use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->table->const_key_parts[use->key]|= use->keypart_map; if (use->keypart != FT_KEYPART) { @@ -4188,7 +4301,8 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, /* Save ptr to first use */ if (!use->table->reginfo.join_tab->keyuse) use->table->reginfo.join_tab->keyuse=save_pos; - use->table->reginfo.join_tab->checked_keys.set_bit(use->key); + if (use->usable) + use->table->reginfo.join_tab->checked_keys.set_bit(use->key); save_pos++; } i=(uint) (save_pos-(KEYUSE*) keyuse->buffer); @@ -4218,7 +4332,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) To avoid bad matches, we don't make ref_table_rows less than 100. */ keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref - if (keyuse->used_tables & + if (keyuse->usable && keyuse->used_tables & (map= (keyuse->used_tables & ~join->const_table_map & ~OUTER_REF_TABLE_BIT))) { @@ -4411,7 +4525,8 @@ best_access_path(JOIN *join, if 1. expression doesn't refer to forward tables 2. we won't get two ref-or-null's */ - if (!(remaining_tables & keyuse->used_tables) && + if (keyuse->usable && + !(remaining_tables & keyuse->used_tables) && !(ref_or_null_part && (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))) { @@ -5915,9 +6030,11 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, uint i; for (i=0 ; i < keyparts ; keyuse++,i++) { - while (keyuse->keypart != i || - ((~used_tables) & keyuse->used_tables)) + while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) || + !keyuse->usable) + { keyuse++; /* Skip other parts */ + } uint maybe_null= test(keyinfo->key_part[i].null_bit); j->ref.items[i]=keyuse->val; // Save for cond removal @@ -16853,8 +16970,11 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, unit; unit= unit->next_unit()) { - if (mysql_explain_union(thd, unit, result)) - DBUG_VOID_RETURN; + if (!(unit->item && unit->item->eliminated)) + { + if (mysql_explain_union(thd, unit, result)) + DBUG_VOID_RETURN; + } } DBUG_VOID_RETURN; } diff --git a/sql/sql_select.h b/sql/sql_select.h index f209cee4956..a1e382d1bcd 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -51,6 +51,7 @@ typedef struct keyuse_t { NULL - Otherwise (the source equality can't be turned off) */ bool *cond_guard; + bool usable; } KEYUSE; class store_key; From c4922cbf10043334262cc4a5c94c8b2e48ce1171 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Wed, 24 Jun 2009 00:06:13 +0400 Subject: [PATCH 12/27] MWL#17: Table elimination - More testcases - Let add_ft_key() set keyuse->usable mysql-test/r/table_elim.result: MWL#17: Table elimination - More testcases mysql-test/t/table_elim.test: MWL#17: Table elimination - More testcases sql/sql_select.cc: MWL#17: Table elimination - Let add_ft_key() set keyuse->usable --- mysql-test/r/table_elim.result | 96 +++++++++++++++++++++++++++++ mysql-test/t/table_elim.test | 69 +++++++++++++++++++++ sql-bench/test-table-elimination.sh | 2 +- sql/sql_select.cc | 1 + 4 files changed, 167 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result index 8ff8786a574..bd35fc3de33 100644 --- a/mysql-test/r/table_elim.result +++ b/mysql-test/r/table_elim.result @@ -1,4 +1,5 @@ drop table if exists t0, t1, t2, t3; +drop view if exists v1, v2; create table t1 (a int); insert into t1 values (0),(1),(2),(3); create table t0 as select * from t1; @@ -76,3 +77,98 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort 1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 Using index drop table t0, t1, t2, t3; +create table t0 ( id integer, primary key (id)); +create table t1 ( +id integer, +attr1 integer, +primary key (id), +key (attr1) +); +create table t2 ( +id integer, +attr2 integer, +fromdate date, +primary key (id, fromdate), +key (attr2,fromdate) +); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +insert into t0 select A.id + 10*B.id from t0 A, t0 B where B.id > 0; +insert into t1 select id, id from t0; +insert into t2 select id, id, date_add('2009-06-22', interval id day) from t0; +insert into t2 select id, id+1, date_add('2008-06-22', interval id day) from t0; +create view v1 as +select +F.id, A1.attr1, A2.attr2 +from +t0 F +left join t1 A1 on A1.id=F.id +left join t2 A2 on A2.id=F.id and +A2.fromdate=(select MAX(fromdate) from +t2 where id=A2.id); +create view v2 as +select +F.id, A1.attr1, A2.attr2 +from +t0 F +left join t1 A1 on A1.id=F.id +left join t2 A2 on A2.id=F.id and +A2.fromdate=(select MAX(fromdate) from +t2 where id=F.id); +This should use one table: +explain select id from v1 where id=2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY F const PRIMARY PRIMARY 4 const 1 Using index +This should use one table: +explain extended select id from v1 where id in (1,2,3,4); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY F range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index +Warnings: +Note 1276 Field or reference 'test.A2.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` where (`F`.`id` in (1,2,3,4)) +This should use facts and A1 tables: +explain extended select id from v1 where attr1 between 12 and 14; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY A1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using where +1 PRIMARY F eq_ref PRIMARY PRIMARY 4 test.A1.id 1 100.00 Using index +Warnings: +Note 1276 Field or reference 'test.A2.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t1` `A1` where ((`F`.`id` = `A1`.`id`) and (`A1`.`attr1` between 12 and 14)) +This should use facts, A2 and its subquery: +explain extended select id from v1 where attr2 between 12 and 14; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY A2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using where +1 PRIMARY F eq_ref PRIMARY PRIMARY 4 test.A2.id 1 100.00 Using index +3 DEPENDENT SUBQUERY t2 ref PRIMARY PRIMARY 4 test.A2.id 2 100.00 Using index +Warnings: +Note 1276 Field or reference 'test.A2.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t2` `A2` where ((`F`.`id` = `A2`.`id`) and (`A2`.`attr2` between 12 and 14) and (`A2`.`fromdate` = (select max(`test`.`t2`.`fromdate`) AS `MAX(fromdate)` from `test`.`t2` where (`test`.`t2`.`id` = `A2`.`id`)))) +This should use one table: +explain select id from v2 where id=2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY F const PRIMARY PRIMARY 4 const 1 Using index +This should use one table: +explain extended select id from v2 where id in (1,2,3,4); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY F range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index +Warnings: +Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` where (`F`.`id` in (1,2,3,4)) +This should use facts and A1 tables: +explain extended select id from v2 where attr1 between 12 and 14; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY A1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using where +1 PRIMARY F eq_ref PRIMARY PRIMARY 4 test.A1.id 1 100.00 Using index +Warnings: +Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t1` `A1` where ((`F`.`id` = `A1`.`id`) and (`A1`.`attr1` between 12 and 14)) +This should use facts, A2 and its subquery: +explain extended select id from v2 where attr2 between 12 and 14; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY A2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using where +1 PRIMARY F eq_ref PRIMARY PRIMARY 4 test.A2.id 1 100.00 Using where; Using index +3 DEPENDENT SUBQUERY t2 ref PRIMARY PRIMARY 4 test.F.id 2 100.00 Using index +Warnings: +Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1 +Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t2` `A2` where ((`F`.`id` = `A2`.`id`) and (`A2`.`attr2` between 12 and 14) and (`A2`.`fromdate` = (select max(`test`.`t2`.`fromdate`) AS `MAX(fromdate)` from `test`.`t2` where (`test`.`t2`.`id` = `F`.`id`)))) +drop view v1, v2; +drop table t0, t1, t2; diff --git a/mysql-test/t/table_elim.test b/mysql-test/t/table_elim.test index 0e4225f8c9a..807fd736bf6 100644 --- a/mysql-test/t/table_elim.test +++ b/mysql-test/t/table_elim.test @@ -3,6 +3,7 @@ # --disable_warnings drop table if exists t0, t1, t2, t3; +drop view if exists v1, v2; --enable_warnings create table t1 (a int); @@ -56,3 +57,71 @@ explain select count(1) from t1 left join t2 on t2.a=t1.a group by t2.a; drop table t0, t1, t2, t3; +# This will stand for elim_facts +create table t0 ( id integer, primary key (id)); + +# Attribute1, non-versioned +create table t1 ( + id integer, + attr1 integer, + primary key (id), + key (attr1) +); + +# Attribute2, time-versioned +create table t2 ( + id integer, + attr2 integer, + fromdate date, + primary key (id, fromdate), + key (attr2,fromdate) +); + +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +insert into t0 select A.id + 10*B.id from t0 A, t0 B where B.id > 0; + +insert into t1 select id, id from t0; +insert into t2 select id, id, date_add('2009-06-22', interval id day) from t0; +insert into t2 select id, id+1, date_add('2008-06-22', interval id day) from t0; + +create view v1 as +select + F.id, A1.attr1, A2.attr2 +from + t0 F + left join t1 A1 on A1.id=F.id + left join t2 A2 on A2.id=F.id and + A2.fromdate=(select MAX(fromdate) from + t2 where id=A2.id); +create view v2 as +select + F.id, A1.attr1, A2.attr2 +from + t0 F + left join t1 A1 on A1.id=F.id + left join t2 A2 on A2.id=F.id and + A2.fromdate=(select MAX(fromdate) from + t2 where id=F.id); + +--echo This should use one table: +explain select id from v1 where id=2; +--echo This should use one table: +explain extended select id from v1 where id in (1,2,3,4); +--echo This should use facts and A1 tables: +explain extended select id from v1 where attr1 between 12 and 14; +--echo This should use facts, A2 and its subquery: +explain extended select id from v1 where attr2 between 12 and 14; + +# Repeat for v2: + +--echo This should use one table: +explain select id from v2 where id=2; +--echo This should use one table: +explain extended select id from v2 where id in (1,2,3,4); +--echo This should use facts and A1 tables: +explain extended select id from v2 where attr1 between 12 and 14; +--echo This should use facts, A2 and its subquery: +explain extended select id from v2 where attr2 between 12 and 14; + +drop view v1, v2; +drop table t0, t1, t2; diff --git a/sql-bench/test-table-elimination.sh b/sql-bench/test-table-elimination.sh index b2464218531..dc8f070eaed 100755 --- a/sql-bench/test-table-elimination.sh +++ b/sql-bench/test-table-elimination.sh @@ -78,7 +78,7 @@ do_many($dbh,$server->create("elim_attr1", ["primary key (id)", "key (attr1)"])); -# Attribute1, time-versioned +# Attribute2, time-versioned do_many($dbh,$server->create("elim_attr2", ["id integer", "attr2 integer", diff --git a/sql/sql_select.cc b/sql/sql_select.cc index eabf2e36f47..36cba4f648a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -4047,6 +4047,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, keyuse.used_tables=cond_func->key_item()->used_tables(); keyuse.optimize= 0; keyuse.keypart_map= 0; + keyuse.usable= TRUE; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } From defbdce7e8c69df85cdd8c630643ef1152330660 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Wed, 24 Jun 2009 13:01:04 +0400 Subject: [PATCH 13/27] More comments --- sql/sql_select.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 36cba4f648a..79fa62fd37c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2477,7 +2477,9 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ } - +/******************************************************************** + * Table elimination code starts + ********************************************************************/ typedef struct st_keyuse_w_needed_reg { KEYUSE *first; @@ -2849,6 +2851,9 @@ static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const DBUG_VOID_RETURN; } +/******************************************************************** + * Table elimination code ends + ********************************************************************/ /* This structure is used to collect info on potentially sargable From e1250b86569f1fc3f634985b16892f5c702acbb7 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Thu, 25 Jun 2009 02:44:14 +0400 Subject: [PATCH 14/27] MWL#17: Table elimination - fix a typo bug in has_eqref_access_candidate() - Adjust test to remove race condition mysql-test/r/mysql-bug41486.result: Adjust test to remove race condition mysql-test/t/mysql-bug41486.test: Adjust test to remove race condition sql/item.cc: MWL#17: Table elimination: fix a typo bug in has_eqref_access_candidate() --- mysql-test/r/mysql-bug41486.result | 3 +++ mysql-test/t/mysql-bug41486.test | 3 ++- sql/item.cc | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/mysql-bug41486.result b/mysql-test/r/mysql-bug41486.result index 02777ab587f..62a6712eae1 100644 --- a/mysql-test/r/mysql-bug41486.result +++ b/mysql-test/r/mysql-bug41486.result @@ -3,6 +3,9 @@ SET @old_max_allowed_packet= @@global.max_allowed_packet; SET @@global.max_allowed_packet = 2 * 1024 * 1024 + 1024; CREATE TABLE t1(data LONGBLOB); INSERT INTO t1 SELECT REPEAT('1', 2*1024*1024); +SELECT COUNT(*) FROM t1; +COUNT(*) +1 SET @old_general_log = @@global.general_log; SET @@global.general_log = 0; SET @@global.general_log = @old_general_log; diff --git a/mysql-test/t/mysql-bug41486.test b/mysql-test/t/mysql-bug41486.test index 6e014bca7d1..e7b0acc1935 100644 --- a/mysql-test/t/mysql-bug41486.test +++ b/mysql-test/t/mysql-bug41486.test @@ -27,7 +27,8 @@ connect (con1, localhost, root,,); CREATE TABLE t1(data LONGBLOB); INSERT INTO t1 SELECT REPEAT('1', 2*1024*1024); - +# The following is to remove the race between end of insert and start of MYSQL_DUMP: +SELECT COUNT(*) FROM t1; let $outfile= $MYSQLTEST_VARDIR/tmp/bug41486.sql; --error 0,1 remove_file $outfile; diff --git a/sql/item.cc b/sql/item.cc index d317b16a264..1a998ee8812 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1935,8 +1935,9 @@ bool Item_field::check_column_usage_processor(uchar *arg) break; } } + return FALSE; } - return FALSE; + return TRUE; } const char *Item_ident::full_name() const From 4102605fba3edab2935b7b1d46b7c7569bd889e7 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Thu, 25 Jun 2009 14:05:53 +0400 Subject: [PATCH 15/27] MWL#17: Table elimination - Moved table elimination code to sql/opt_table_elimination.cc - Added comments .bzrignore: MWL#17: Table elimination - Moved table elimination code to sql/opt_table_elimination.cc libmysqld/Makefile.am: MWL#17: Table elimination - Moved table elimination code to sql/opt_table_elimination.cc sql/CMakeLists.txt: MWL#17: Table elimination - Moved table elimination code to sql/opt_table_elimination.cc sql/Makefile.am: MWL#17: Table elimination - Moved table elimination code to sql/opt_table_elimination.cc --- .bzrignore | 1 + libmysqld/Makefile.am | 2 +- sql/CMakeLists.txt | 2 +- sql/Makefile.am | 3 +- sql/item.cc | 16 +- sql/item.h | 9 +- sql/item_subselect.cc | 2 +- sql/item_sum.h | 7 +- sql/opt_table_elimination.cc | 493 +++++++++++++++++++++++++++++++++++ sql/sql_select.cc | 400 +--------------------------- sql/sql_select.h | 17 ++ 11 files changed, 548 insertions(+), 404 deletions(-) create mode 100644 sql/opt_table_elimination.cc diff --git a/.bzrignore b/.bzrignore index 392572a3f24..5c944d0d9fa 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1905,3 +1905,4 @@ sql/share/swedish sql/share/ukrainian libmysqld/examples/mysqltest.cc extra/libevent/event-config.h +libmysqld/opt_table_elimination.cc diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index d8b6d0c6131..2847aa1ffd0 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -76,7 +76,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ sql_tablespace.cc \ rpl_injector.cc my_user.c partition_info.cc \ - sql_servers.cc event_parse_data.cc + sql_servers.cc event_parse_data.cc opt_table_elimination.cc libmysqld_int_a_SOURCES= $(libmysqld_sources) nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 83872803dd4..6c15f607f58 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -73,7 +73,7 @@ ADD_EXECUTABLE(mysqld partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc rpl_rli.cc rpl_mi.cc sql_servers.cc sql_connect.cc scheduler.cc - sql_profile.cc event_parse_data.cc + sql_profile.cc event_parse_data.cc opt_table_elimination.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h diff --git a/sql/Makefile.am b/sql/Makefile.am index a3559b38ce4..1237bcb21cb 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -121,7 +121,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ event_queue.cc event_db_repository.cc events.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ - sql_servers.cc event_parse_data.cc + sql_servers.cc event_parse_data.cc \ + opt_table_elimination.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/item.cc b/sql/item.cc index d317b16a264..3cecb8b6e34 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1915,17 +1915,22 @@ void Item_field::reset_field(Field *f) name= (char*) f->field_name; } + bool Item_field::check_column_usage_processor(uchar *arg) { Field_processor_info* info=(Field_processor_info*)arg; + + /* It is ok if this is a column of an allowed table: */ if (used_tables() & ~info->allowed_tables) return FALSE; if (field->table == info->table) { + /* It is not ok to use columns that are not part of the key of interest: */ if (!(field->part_of_key.is_set(info->keyno))) return TRUE; - + + /* Find which key part we're using and mark it in needed_key_parts */ KEY *key= &field->table->key_info[info->keyno]; for (uint part= 0; part < key->key_parts; part++) { @@ -1935,10 +1940,17 @@ bool Item_field::check_column_usage_processor(uchar *arg) break; } } + return FALSE; } - return FALSE; + + /* + We get here when this refers to a table that's neither the table of + interest, nor one of the allowed tables. + */ + return TRUE; } + const char *Item_ident::full_name() const { char *tmp; diff --git a/sql/item.h b/sql/item.h index e9fb39443bc..6f0bf02bdae 100644 --- a/sql/item.h +++ b/sql/item.h @@ -731,7 +731,11 @@ public: virtual bool val_bool_result() { return val_bool(); } virtual bool is_null_result() { return is_null(); } - /* bit map of tables used by item */ + /* + Bitmap of tables used by item + (note: if you need to check dependencies on individual columns, check out + check_column_usage_processor) + */ virtual table_map used_tables() const { return (table_map) 0L; } /* Return table map of tables that can't be NULL tables (tables that are @@ -1013,7 +1017,7 @@ public: bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs); }; - +/* Data for Item::check_column_usage_processor */ typedef struct { table_map allowed_tables; @@ -1022,6 +1026,7 @@ typedef struct uint needed_key_parts; } Field_processor_info; + class sp_head; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 7559a183e60..45b540f5009 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -250,7 +250,7 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery, if (lex->having && (lex->having)->walk(processor, walk_subquery, argument)) return 1; - /* TODO: why doesn't this walk the OUTER JOINs' ON expressions */ + /* TODO: why does this walk WHERE/HAVING but not ON expressions of outer joins? */ while ((item=li++)) { diff --git a/sql/item_sum.h b/sql/item_sum.h index aec5830f381..e884452d6e6 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -351,9 +351,10 @@ public: Return bitmap of tables that are needed to evaluate the item. The implementation takes into account the used strategy: items resolved - at optimization phase report 0. - Items that depend on the number of rows only, e.g. COUNT(*) will report - zero, but will still false from const_item(). + at optimization phase will report 0. + Items that depend on the number of join output records, but not columns + of any particular table (like COUNT(*)) will report 0 from used_tables(), + but will still return false from const_item(). */ table_map used_tables() const { return used_tables_cache; } void update_used_tables (); diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc new file mode 100644 index 00000000000..2ba287ea1f8 --- /dev/null +++ b/sql/opt_table_elimination.cc @@ -0,0 +1,493 @@ +/** + @file + + @brief + Table Elimination Module + + @defgroup Table_Elimination Table Elimination Module + @{ +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mysql_priv.h" +#include "sql_select.h" + +/* + OVERVIEW + The module has one entry point - eliminate_tables() function, which one + needs to call (once) sometime after update_ref_and_keys() but before the + join optimization. + eliminate_tables() operates over the JOIN structures. Logically, it + removes the right sides of outer join nests. Physically, it changes the + following members: + + * Eliminated tables are marked as constant and moved to the front of the + join order. + * In addition to this, they are recorded in JOIN::eliminated_tables bitmap. + + * All join nests have their NESTED_JOIN::n_tables updated to discount + the eliminated tables + + * Items that became disused because they were in the ON expression of an + eliminated outer join are notified by means of the Item tree walk which + calls Item::mark_as_eliminated_processor for every item + - At the moment the only Item that cares is Item_subselect with its + Item_subselect::eliminated flag which is used by EXPLAIN code to + check if the subquery should be shown in EXPLAIN. + + Table elimination is intended to be done on every PS re-execution. +*/ + +static int +eliminate_tables_for_join_list(JOIN *join, List *join_list, + table_map used_tables_elsewhere, + uint *const_tbl_count, table_map *const_tables); +static bool table_has_one_match(TABLE *table, table_map bound_tables); +static void +mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, + table_map *const_tables); +static bool +extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, + KEYUSE *key_start, KEYUSE *key_end, + uint n_keyuses, table_map bound_parts); + +/* + Perform table elimination + + SYNOPSIS + eliminate_tables() + join Join to work on + const_tbl_count INOUT Number of constant tables (this includes + eliminated tables) + const_tables INOUT Bitmap of constant tables + + DESCRIPTION + + TODO fix comment + + SELECT * FROM t1 LEFT JOIN + (t2 JOIN t3) ON t3.primary_key=t1.col AND + t4.primary_key= t2.col + + CRITERIA FOR REMOVING ONE OJ NEST + we can't rely on sole presense of eq_refs. Because if we do, we'll miss + things like this: + + SELECT * FROM flights LEFT JOIN + (pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse) + + (no-polygamy schema/query but there can be many couples on the flight) + .. + + REMOVAL PROCESS + We can remove an inner side of an outer join if it there is a warranty + that it will produce not more than one record: + + ... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ... + + For nested outer joins: + - The process naturally occurs bottom-up (in order to remove an + outer-join we need to analyze its contents) + - If we failed to remove an outer join nest, it makes no sense to + try removing its ancestors, as the + ot LEFT JOIN it ON cond + pair may possibly produce two records (one record via match and + another one as access-method record). + + Q: If we haven't removed an OUTER JOIN, does it make sense to attempt + removing its ancestors? + A: No as the innermost outer join will produce two records => no ancestor + outer join nest will be able to provide the max_fanout==1 guarantee. +*/ + +void eliminate_tables(JOIN *join, uint *const_tbl_count, + table_map *const_tables) +{ + Item *item; + table_map used_tables; + DBUG_ENTER("eliminate_tables"); + + DBUG_ASSERT(join->eliminated_tables == 0); + + /* MWL#17 is only about outer join elimination, so: */ + if (!join->outer_join) + DBUG_VOID_RETURN; + + /* Find the tables that are referred to from WHERE/HAVING */ + used_tables= (join->conds? join->conds->used_tables() : 0) | + (join->having? join->having->used_tables() : 0); + + /* Add tables referred to from the select list */ + List_iterator it(join->fields_list); + while ((item= it++)) + used_tables |= item->used_tables(); + + /* Add tables referred to from ORDER BY and GROUP BY lists */ + ORDER *all_lists[]= { join->order, join->group_list}; + for (int i=0; i < 2; i++) + { + for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next) + used_tables |= (*(cur_list->item))->used_tables(); + } + + THD* thd= join->thd; + if (join->select_lex == &thd->lex->select_lex) + { + /* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */ + used_tables |= thd->table_map_for_update; + + /* Multi-table UPDATE: don't eliminate tables referred from SET statement */ + if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI) + { + List_iterator it2(thd->lex->value_list); + while ((item= it2++)) + used_tables |= item->used_tables(); + } + } + + if (((1 << join->tables) - 1) & ~used_tables) + { + /* There are some time tables that we probably could eliminate */ + eliminate_tables_for_join_list(join, join->join_list, used_tables, + const_tbl_count, const_tables); + } + DBUG_VOID_RETURN; +} + + + + +/* + Now on to traversal. There can be a situation like this: + + FROM t1 + LEFT JOIN t2 ON cond(t1,t2) + LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*) + LEFT JOIN t4 ON cond(..., possibly-t2) + + Besides that, simplify_joins() may have created back references, so when + we're e.g. looking at outer join (*) we need to look both forward and + backward to check if there are any references in preceding/following + outer joins' + + TODO would it create only following-sibling references or + preceding-sibling as well? + And if not, should we rely on that? + +*/ + +static int +eliminate_tables_for_join_list(JOIN *join, List *join_list, + table_map used_tables_elsewhere, + uint *const_tbl_count, table_map *const_tables) +{ + List_iterator it(*join_list); + table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca + table_map used_tables_on_left; + TABLE_LIST *tbl; + int i, n_tables; + int eliminated=0; + + /* Collect the reverse-bitmap-array */ + for (i=0; (tbl= it++); i++) + { + used_tables_on_right[i]= 0; + if (tbl->on_expr) + used_tables_on_right[i]= tbl->on_expr->used_tables(); + if (tbl->nested_join) + used_tables_on_right[i]= tbl->nested_join->used_tables; + } + n_tables= i; + + for (i= n_tables - 2; i > 0; i--) + used_tables_on_right[i] |= used_tables_on_right[i+1]; + + it.rewind(); + + /* Walk through tables and join nests and see if we can eliminate them */ + used_tables_on_left= 0; + i= 1; + while ((tbl= it++)) + { + table_map tables_used_outside= used_tables_on_left | + used_tables_on_right[i] | + used_tables_elsewhere; + table_map cur_tables= 0; + + if (tbl->nested_join) + { + DBUG_ASSERT(tbl->on_expr); + /* + There can be cases where table removal is applicable for tables + within the outer join but not for the outer join itself. Ask to + remove the children first. + + TODO: NoHopelessEliminationAttempts: the below call can return + information about whether it would make any sense to try removing + this entire outer join nest. + */ + int eliminated_in_children= + eliminate_tables_for_join_list(join, &tbl->nested_join->join_list, + tables_used_outside, + const_tbl_count, const_tables); + tbl->nested_join->n_tables -=eliminated_in_children; + cur_tables= tbl->nested_join->used_tables; + if (!(cur_tables & tables_used_outside)) + { + /* + Check if all embedded tables together can produce at most one + record combination. This is true when + - each of them has one_match(outer-tables) property + (this is a stronger condition than all of them together having + this property but that's irrelevant here) + - there are no outer joins among them + (except for the case of outer join which has all inner tables + to be constant and is guaranteed to produce only one record. + that record will be null-complemented) + */ + bool one_match= TRUE; + List_iterator it2(tbl->nested_join->join_list); + TABLE_LIST *inner; + while ((inner= it2++)) + { + /* + Bail out if we see an outer join (TODO: handle the above + null-complemntated-rows-only case) + */ + if (inner->on_expr) + { + one_match= FALSE; + break; + } + + if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts + !table_has_one_match(inner->table, + ~tbl->nested_join->used_tables)) + { + one_match= FALSE; + break; + } + } + if (one_match) + { + it2.rewind(); + while ((inner= it2++)) + { + mark_table_as_eliminated(join, inner->table, const_tbl_count, + const_tables); + } + eliminated += tbl->nested_join->join_list.elements; + //psergey-todo: do we need to do anything about removing the join + //nest? + tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); + } + else + { + eliminated += eliminated_in_children; + } + } + } + else if (tbl->on_expr) + { + cur_tables= tbl->on_expr->used_tables(); + /* Check and remove */ + if (!(tbl->table->map & tables_used_outside) && + table_has_one_match(tbl->table, (table_map)-1)) + { + mark_table_as_eliminated(join, tbl->table, const_tbl_count, + const_tables); + tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); + eliminated += 1; + } + } + + /* Update bitmap of tables we've seen on the left */ + i++; + used_tables_on_left |= cur_tables; + } + return eliminated; +} + + +/* + Mark table as eliminated: + - Mark it as constant table + - Move it to the front of join order + - Record it in join->eliminated_tables +*/ + +static +void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, + table_map *const_tables) +{ + JOIN_TAB *tab= table->reginfo.join_tab; + if (!(*const_tables & tab->table->map)) + { + DBUG_PRINT("info", ("Eliminated table %s", table->alias)); + tab->type= JT_CONST; + join->eliminated_tables |= table->map; + *const_tables |= table->map; + join->const_table_map|= table->map; + set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); + } +} + + +/* + Check the table will produce at most one matching record + + SYNOPSIS + table_has_one_match() + table The [base] table being checked + bound_tables Tables that should be considered bound. + + DESCRIPTION + Check if the given table will produce at most one matching record for + each record combination of tables in bound_tables. + + RETURN + TRUE Yes, at most one match + FALSE No +*/ + +static bool table_has_one_match(TABLE *table, table_map bound_tables) +{ + KEYUSE *keyuse= table->reginfo.join_tab->keyuse; + if (keyuse) + { + /* + Walk through all of the KEYUSE elements and + - locate unique keys + - check if we have eq_ref access for them + TODO any other reqs? + loops are constructed like in best_access_path + */ + while (keyuse->table == table) + { + uint key= keyuse->key; + key_part_map bound_parts=0; + uint n_unusable=0; + bool ft_key= test(keyuse->keypart == FT_KEYPART); + KEY *keyinfo= table->key_info + key; + KEYUSE *key_start = keyuse; + + do /* For each keypart and each way to read it */ + { + if (keyuse->usable) + { + if(!(keyuse->used_tables & ~bound_tables) && + !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) + { + bound_parts |= keyuse->keypart_map; + } + } + else + n_unusable++; + keyuse++; + } while (keyuse->table == table && keyuse->key == key); + + if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) + != HA_NOSAME)) + { + continue; + } + + if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts) || + extra_keyuses_bind_all_keyparts(bound_tables, table, key_start, + keyuse, n_unusable, bound_parts)) + { + return TRUE; + } + } + } + return FALSE; +} + + +typedef struct st_keyuse_w_needed_reg +{ + KEYUSE *keyuse; + key_part_map dependency_parts; +} Keyuse_w_needed_reg; + + +/* + SYNOPSIS + extra_keyuses_bind_all_keyparts() + bound_tables Tables which can be considered constants + table Table we're examining + key_start Start of KEYUSE array with elements describing the key + of interest + key_end End of the array + 1 + n_keyuses Number + bound_parts Key parts whose values are known to be bound. + + DESCRIPTION + Check if unusable KEYUSE elements cause all parts of key to be bound. An + unusable keyuse element makes a keypart bound when it + represents the following: + + keyXpartY=func(bound_columns, preceding_tables) + + RETURN + TRUE Yes, at most one match + FALSE No +*/ + +static bool +extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, + KEYUSE *key_start, KEYUSE *key_end, + uint n_keyuses, table_map bound_parts) +{ + if (n_keyuses && bound_parts) + { + KEY *keyinfo= table->key_info + key_start->key; + Keyuse_w_needed_reg *uses; + if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)* + n_keyuses))) + return FALSE; + uint n_uses=0; + /* First, collect an array*/ + for (KEYUSE *k= key_start; k!=key_end; k++) + { + if (!k->usable && !(k->used_tables & ~bound_tables)) + { + Field_processor_info fp= {bound_tables, table, k->key, 0}; + if (!k->val->walk(&Item::check_column_usage_processor, FALSE, + (uchar*)&fp)) + { + uses[n_uses].keyuse= k; + uses[n_uses].dependency_parts= fp.needed_key_parts; + n_uses++; + } + } + } + + /* Now compute transitive closure */ + uint n_bounded; + do + { + n_bounded= 0; + for (uint i=0; i< n_uses; i++) + { + /* needed_parts is covered by what is already bound*/ + if (!(uses[i].dependency_parts & ~bound_parts)) + { + bound_parts|= key_part_map(1) << uses[i].keyuse->keypart; + n_bounded++; + } + if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) + return TRUE; + } + } while (n_bounded != 0); + } + return FALSE; +} + +/** + @} (end of group Table_Elimination) +*/ + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 79fa62fd37c..4d85570b6d2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -42,11 +42,6 @@ #define TMP_ENGINE_HTON myisam_hton #endif -#define FT_KEYPART (MAX_REF_PARTS+10) -/* Values in optimize */ -#define KEY_OPTIMIZE_EXISTS 1 -#define KEY_OPTIMIZE_REF_OR_NULL 2 - const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "MAYBE_REF","ALL","range","index","fulltext", "ref_or_null","unique_subquery","index_subquery", @@ -65,7 +60,6 @@ static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, table_map table_map, SELECT_LEX *select_lex, st_sargable_param **sargables); static int sort_keyuse(KEYUSE *a,KEYUSE *b); -static void set_position(JOIN *join,uint index,JOIN_TAB *table,KEYUSE *key); static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, table_map used_tables); static bool choose_plan(JOIN *join,table_map join_tables); @@ -2386,10 +2380,13 @@ mysql_select(THD *thd, Item ***rref_pointer_array, } else { - // psergey{ + /* + When in EXPLAIN, delay deleting the joins so that they are still + available when we're producing EXPLAIN EXTENDED warning text. + */ if (select_options & SELECT_DESCRIBE) free_join= 0; - // }psergey + if (!(join= new JOIN(thd, fields, select_options, result))) DBUG_RETURN(TRUE); thd_proc_info(thd, "init"); @@ -2477,383 +2474,6 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ } -/******************************************************************** - * Table elimination code starts - ********************************************************************/ -typedef struct st_keyuse_w_needed_reg -{ - KEYUSE *first; - key_part_map second; - -} Keyuse_w_needed_reg; - -static -bool has_eq_ref_access_candidate(TABLE *table, table_map can_refer_to_these) -{ - KEYUSE *keyuse= table->reginfo.join_tab->keyuse; - if (keyuse) - { - /* - walk through all of the KEYUSE elements and - - locate unique keys - - check if we have eq_ref access for them - TODO any other reqs? - loops are constructed like in best_access_path - */ - while (keyuse->table == table) - { - uint key= keyuse->key; - key_part_map bound_parts=0; - uint n_unusable=0; - bool ft_key= test(keyuse->keypart == FT_KEYPART); - KEY *keyinfo= table->key_info + key; - KEYUSE *key_start = keyuse; - - do /* For each keypart and each way to read it */ - { - if (keyuse->usable) - { - if(!(keyuse->used_tables & ~can_refer_to_these) && - !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) - { - bound_parts |= keyuse->keypart_map; - } - } - else - n_unusable++; - keyuse++; - } while (keyuse->table == table && keyuse->key == key); - - if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) - != HA_NOSAME)) - { - continue; - } - - if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) - return TRUE; - /* - Ok, usable keyuse elements didn't help us. Try making use of - unusable KEYUSEs (psergey-todo: sane comments:) - */ - if (n_unusable && bound_parts) - { - /* - Check if unusable KEYUSE elements cause all parts of key to be - bound. An unusable keyuse element makes a key part bound when it - represents the following: - - keyXpartY=func(bound_columns, preceding_tables) - - . - */ - Keyuse_w_needed_reg *uses; - if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)*n_unusable))) - return FALSE; - uint n_uses=0; - for (KEYUSE *k= key_start; k!=keyuse; k++) - { - if (!k->usable && !(k->used_tables & ~can_refer_to_these)) - { - //Walk k->val and check which key parts it depends on. - Field_processor_info fp= {can_refer_to_these, table, k->key, 0}; - if (!k->val->walk(&Item::check_column_usage_processor, FALSE, - (uchar*)&fp)) - { - uses[n_uses].first= k; - uses[n_uses].second= fp.needed_key_parts; - n_uses++; - } - } - } - /* Now compute transitive closure */ - uint n_bounded; - do - { - n_bounded= 0; - for (uint i=0; i< n_uses; i++) - { - /* needed_parts is covered by what is already bound*/ - if (!(uses[i].second & ~bound_parts)) - { - bound_parts|= key_part_map(1) << uses[i].first->keypart; - n_bounded++; - } - if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) - return TRUE; - } - } while (n_bounded != 0); - } - } - } - return FALSE; -} - - -static void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, - table_map *const_tables) -{ - JOIN_TAB *tab= table->reginfo.join_tab; - if (!(*const_tables & tab->table->map)) - { - DBUG_PRINT("info", ("Eliminated table %s", table->alias)); - tab->type= JT_CONST; - join->eliminated_tables |= table->map; - *const_tables |= table->map; - join->const_table_map|= table->map; - set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); - } -} - - -/* - Now on to traversal. There can be a situation like this: - - FROM t1 - LEFT JOIN t2 ON cond(t1,t2) - LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*) - LEFT JOIN t4 ON cond(..., possibly-t2) - - Besides that, simplify_joins() may have created back references, so when - we're e.g. looking at outer join (*) we need to look both forward and - backward to check if there are any references in preceding/following - outer joins' - - TODO would it create only following-sibling references or - preceding-sibling as well? - And if not, should we rely on that? - -*/ - -int -eliminate_tables_for_join_list(JOIN *join, List *join_list, - table_map used_tables_elsewhere, - uint *const_tbl_count, table_map *const_tables) -{ - List_iterator it(*join_list); - table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca - table_map used_tables_on_left; - TABLE_LIST *tbl; - int i, n_tables; - int eliminated=0; - - /* Collect the reverse-bitmap-array */ - for (i=0; (tbl= it++); i++) - { - used_tables_on_right[i]= 0; - if (tbl->on_expr) - used_tables_on_right[i]= tbl->on_expr->used_tables(); - if (tbl->nested_join) - used_tables_on_right[i]= tbl->nested_join->used_tables; - } - n_tables= i; - - for (i= n_tables - 2; i > 0; i--) - used_tables_on_right[i] |= used_tables_on_right[i+1]; - - it.rewind(); - - /* Walk through tables and join nests and see if we can eliminate them */ - used_tables_on_left= 0; - i= 1; - while ((tbl= it++)) - { - table_map tables_used_outside= used_tables_on_left | - used_tables_on_right[i] | - used_tables_elsewhere; - table_map cur_tables= 0; - - if (tbl->nested_join) - { - DBUG_ASSERT(tbl->on_expr); - /* - There can be cases where table removal is applicable for tables - within the outer join but not for the outer join itself. Ask to - remove the children first. - - TODO: NoHopelessEliminationAttempts: the below call can return - information about whether it would make any sense to try removing - this entire outer join nest. - */ - int eliminated_in_children= - eliminate_tables_for_join_list(join, &tbl->nested_join->join_list, - tables_used_outside, - const_tbl_count, const_tables); - tbl->nested_join->n_tables -=eliminated_in_children; - cur_tables= tbl->nested_join->used_tables; - if (!(cur_tables & tables_used_outside)) - { - /* - Check if all embedded tables together can produce at most one - record combination. This is true when - - each of them has one_match(outer-tables) property - (this is a stronger condition than all of them together having - this property but that's irrelevant here) - - there are no outer joins among them - (except for the case of outer join which has all inner tables - to be constant and is guaranteed to produce only one record. - that record will be null-complemented) - */ - bool one_match= TRUE; - List_iterator it2(tbl->nested_join->join_list); - TABLE_LIST *inner; - while ((inner= it2++)) - { - /* - Bail out if we see an outer join (TODO: handle the above - null-complemntated-rows-only case) - */ - if (inner->on_expr) - { - one_match= FALSE; - break; - } - - if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts - !has_eq_ref_access_candidate(inner->table, - ~tbl->nested_join->used_tables)) - { - one_match= FALSE; - break; - } - } - if (one_match) - { - it2.rewind(); - while ((inner= it2++)) - { - mark_table_as_eliminated(join, inner->table, const_tbl_count, - const_tables); - } - eliminated += tbl->nested_join->join_list.elements; - //psergey-todo: do we need to do anything about removing the join - //nest? - tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); - } - else - { - eliminated += eliminated_in_children; - } - } - } - else if (tbl->on_expr) - { - cur_tables= tbl->on_expr->used_tables(); - /* Check and remove */ - if (!(tbl->table->map & tables_used_outside) && - has_eq_ref_access_candidate(tbl->table, (table_map)-1)) - { - mark_table_as_eliminated(join, tbl->table, const_tbl_count, - const_tables); - tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); - eliminated += 1; - } - } - - /* Update bitmap of tables we've seen on the left */ - i++; - used_tables_on_left |= cur_tables; - } - return eliminated; -} - - -/* - Perform table elimination based on outer join - - SELECT * FROM t1 LEFT JOIN - (t2 JOIN t3) ON t3.primary_key=t1.col AND - t4.primary_key= t2.col - - CRITERIA FOR REMOVING ONE OJ NEST - we can't rely on sole presense of eq_refs. Because if we do, we'll miss - things like this: - - SELECT * FROM flights LEFT JOIN - (pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse) - - (no-polygamy schema/query but there can be many couples on the flight) - .. - - REMOVAL PROCESS - We can remove an inner side of an outer join if it there is a warranty - that it will produce not more than one record: - - ... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ... - - For nested outer joins: - - The process naturally occurs bottom-up (in order to remove an - outer-join we need to analyze its contents) - - If we failed to remove an outer join nest, it makes no sense to - try removing its ancestors, as the - ot LEFT JOIN it ON cond - pair may possibly produce two records (one record via match and - another one as access-method record). - - Q: If we haven't removed an OUTER JOIN, does it make sense to attempt - removing its ancestors? - A: No as the innermost outer join will produce two records => no ancestor - outer join nest will be able to provide the max_fanout==1 guarantee. - - psergey-todo: . -*/ - -static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const_tables) -{ - Item *item; - table_map used_tables; - DBUG_ENTER("eliminate_tables"); - - DBUG_ASSERT(join->eliminated_tables == 0); - - /* MWL#17 is only about outer join elimination, so: */ - if (!join->outer_join) - DBUG_VOID_RETURN; - - /* Find the tables that are referred to from WHERE/HAVING */ - used_tables= (join->conds? join->conds->used_tables() : 0) | - (join->having? join->having->used_tables() : 0); - - /* Add tables referred to from the select list */ - List_iterator it(join->fields_list); - while ((item= it++)) - used_tables |= item->used_tables(); - - /* Add tables referred to from ORDER BY and GROUP BY lists */ - ORDER *all_lists[]= { join->order, join->group_list}; - for (int i=0; i < 2; i++) - { - for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next) - used_tables |= (*(cur_list->item))->used_tables(); - } - - THD* thd= join->thd; - if (join->select_lex == &thd->lex->select_lex) - { - /* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */ - used_tables |= thd->table_map_for_update; - - /* Multi-table UPDATE: don't eliminate tables referred from SET statement */ - if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI) - { - List_iterator it2(thd->lex->value_list); - while ((item= it2++)) - used_tables |= item->used_tables(); - } - } - - if (((1 << join->tables) - 1) & ~used_tables) - { - /* There are some time tables that we probably could eliminate */ - eliminate_tables_for_join_list(join, join->join_list, used_tables, - const_tbl_count, const_tables); - } - DBUG_VOID_RETURN; -} - -/******************************************************************** - * Table elimination code ends - ********************************************************************/ /* This structure is used to collect info on potentially sargable @@ -3219,10 +2839,6 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, } } - //psergey-todo: table elimination - //eliminate_tables(join, &const_count, &found_const_table_map); - //:psergey-todo - /* Calc how many (possible) matched records in each table */ for (s=stat ; s < stat_end ; s++) @@ -3354,7 +2970,7 @@ typedef struct key_field_t { */ bool null_rejecting; bool *cond_guard; /* See KEYUSE::cond_guard */ - bool usable; + bool usable; /* See KEYUSE::usable */ } KEY_FIELD; @@ -4428,8 +4044,7 @@ add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab) /** Save const tables first as used tables. */ -static void -set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key) +void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key) { join->positions[idx].table= table; join->positions[idx].key=key; @@ -17021,7 +16636,6 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) unit->fake_select_lex->options|= SELECT_DESCRIBE; if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) res= unit->exec(); - //psergey-move: res|= unit->cleanup(); } else { diff --git a/sql/sql_select.h b/sql/sql_select.h index a1e382d1bcd..9bce799b02d 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -28,6 +28,11 @@ #include "procedure.h" #include +#define FT_KEYPART (MAX_REF_PARTS+10) +/* Values in optimize */ +#define KEY_OPTIMIZE_EXISTS 1 +#define KEY_OPTIMIZE_REF_OR_NULL 2 + typedef struct keyuse_t { TABLE *table; Item *val; /**< or value if no field */ @@ -51,6 +56,14 @@ typedef struct keyuse_t { NULL - Otherwise (the source equality can't be turned off) */ bool *cond_guard; + /* + TRUE <=> This keyuse can be used to construct key access. + FALSE <=> Otherwise. Currently unusable KEYUSEs represent equalities + where one table column refers to another one, like this: + t.keyXpartA=func(t.keyXpartB) + This equality cannot be used for index access but is useful + for table elimination. + */ bool usable; } KEYUSE; @@ -734,9 +747,13 @@ bool error_if_full_join(JOIN *join); int report_error(TABLE *table, int error); int safe_index_read(JOIN_TAB *tab); COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value); +void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key); inline bool optimizer_flag(THD *thd, uint flag) { return (thd->variables.optimizer_switch & flag); } +void eliminate_tables(JOIN *join, uint *const_tbl_count, + table_map *const_tables); + From d764108a2c259e1e38487eda1dfa7ac894b9e5a5 Mon Sep 17 00:00:00 2001 From: Sergey Petrunia Date: Fri, 26 Jun 2009 00:07:29 +0400 Subject: [PATCH 16/27] MWL#17: Table elimination - Better comments, variable/function renames --- sql/opt_table_elimination.cc | 230 ++++++++++++++++++++--------------- sql/sql_select.cc | 7 +- sql/sql_select.h | 3 +- 3 files changed, 141 insertions(+), 99 deletions(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 2ba287ea1f8..26ebb2232c4 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -17,33 +17,34 @@ /* OVERVIEW - The module has one entry point - eliminate_tables() function, which one - needs to call (once) sometime after update_ref_and_keys() but before the - join optimization. - eliminate_tables() operates over the JOIN structures. Logically, it - removes the right sides of outer join nests. Physically, it changes the - following members: - * Eliminated tables are marked as constant and moved to the front of the - join order. - * In addition to this, they are recorded in JOIN::eliminated_tables bitmap. + The module has one entry point - eliminate_tables() function, which one + needs to call (once) sometime after update_ref_and_keys() but before the + join optimization. + eliminate_tables() operates over the JOIN structures. Logically, it + removes the right sides of outer join nests. Physically, it changes the + following members: - * All join nests have their NESTED_JOIN::n_tables updated to discount - the eliminated tables + * Eliminated tables are marked as constant and moved to the front of the + join order. + * In addition to this, they are recorded in JOIN::eliminated_tables bitmap. - * Items that became disused because they were in the ON expression of an - eliminated outer join are notified by means of the Item tree walk which - calls Item::mark_as_eliminated_processor for every item - - At the moment the only Item that cares is Item_subselect with its - Item_subselect::eliminated flag which is used by EXPLAIN code to - check if the subquery should be shown in EXPLAIN. + * All join nests have their NESTED_JOIN::n_tables updated to discount + the eliminated tables - Table elimination is intended to be done on every PS re-execution. + * Items that became disused because they were in the ON expression of an + eliminated outer join are notified by means of the Item tree walk which + calls Item::mark_as_eliminated_processor for every item + - At the moment the only Item that cares is Item_subselect with its + Item_subselect::eliminated flag which is used by EXPLAIN code to + check if the subquery should be shown in EXPLAIN. + + Table elimination is redone on every PS re-execution. */ static int eliminate_tables_for_join_list(JOIN *join, List *join_list, - table_map used_tables_elsewhere, + table_map tables_used_elsewhere, uint *const_tbl_count, table_map *const_tables); static bool table_has_one_match(TABLE *table, table_map bound_tables); static void @@ -65,42 +66,33 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, const_tables INOUT Bitmap of constant tables DESCRIPTION + This function is the entry point for table elimination. + The idea behind table elimination is that if we have an outer join: - TODO fix comment + SELECT * FROM t1 LEFT JOIN + (t2 JOIN t3) ON t3.primary_key=t1.col AND + t4.primary_key=t2.col + such that + + 1. columns of the inner tables are not used anywhere ouside the outer + join (not in WHERE, not in GROUP/ORDER BY clause, not in select list + etc etc), and + 2. inner side of the outer join is guaranteed to produce at most one + record combination for each record combination of outer tables. + + then the inner side of the outer join can be removed from the query. + This is because it will always produce one matching record (either a + real match or a NULL-complemented record combination), and since there + are no references to columns of the inner tables anywhere, it doesn't + matter which record combination it was. + + This function primary handles checking #1. It collects a bitmap of + tables that are not used in select list/GROUP BY/ORDER BY/HAVING/etc and + thus can possibly be eliminated. - SELECT * FROM t1 LEFT JOIN - (t2 JOIN t3) ON t3.primary_key=t1.col AND - t4.primary_key= t2.col + SIDE EFFECTS + See the OVERVIEW section at the top of this file. - CRITERIA FOR REMOVING ONE OJ NEST - we can't rely on sole presense of eq_refs. Because if we do, we'll miss - things like this: - - SELECT * FROM flights LEFT JOIN - (pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse) - - (no-polygamy schema/query but there can be many couples on the flight) - .. - - REMOVAL PROCESS - We can remove an inner side of an outer join if it there is a warranty - that it will produce not more than one record: - - ... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ... - - For nested outer joins: - - The process naturally occurs bottom-up (in order to remove an - outer-join we need to analyze its contents) - - If we failed to remove an outer join nest, it makes no sense to - try removing its ancestors, as the - ot LEFT JOIN it ON cond - pair may possibly produce two records (one record via match and - another one as access-method record). - - Q: If we haven't removed an OUTER JOIN, does it make sense to attempt - removing its ancestors? - A: No as the innermost outer join will produce two records => no ancestor - outer join nest will be able to provide the max_fanout==1 guarantee. */ void eliminate_tables(JOIN *join, uint *const_tbl_count, @@ -112,7 +104,7 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count, DBUG_ASSERT(join->eliminated_tables == 0); - /* MWL#17 is only about outer join elimination, so: */ + /* If there are no outer joins, we have nothing to eliminate: */ if (!join->outer_join) DBUG_VOID_RETURN; @@ -150,7 +142,7 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count, if (((1 << join->tables) - 1) & ~used_tables) { - /* There are some time tables that we probably could eliminate */ + /* There are some tables that we probably could eliminate. Try it. */ eliminate_tables_for_join_list(join, join->join_list, used_tables, const_tbl_count, const_tables); } @@ -161,37 +153,70 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count, /* - Now on to traversal. There can be a situation like this: + Perform table elimination in a given join list - FROM t1 - LEFT JOIN t2 ON cond(t1,t2) - LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*) - LEFT JOIN t4 ON cond(..., possibly-t2) + SYNOPSIS + eliminate_tables_for_join_list() + join The join + join_list Join list to work on + tables_used_elsewhere Bitmap of tables that are referred to from + somewhere outside of the join list (e.g. + select list, HAVING, etc). + const_tbl_count INOUT Number of constant tables (eliminated tables + are considered constant) + const_tables INOUT Bitmap of constant tables. - Besides that, simplify_joins() may have created back references, so when - we're e.g. looking at outer join (*) we need to look both forward and - backward to check if there are any references in preceding/following - outer joins' + DESCRIPTION + Try eliminating members of the given join list (and its children, + recursively). - TODO would it create only following-sibling references or - preceding-sibling as well? - And if not, should we rely on that? - + Search for tables to be eliminated is performed on recursive descent, + while the elimination is done on ascent. + + DESCENT AND NO-REFERENCES CHECK + The descent part is needed because of the following: consider a join list + + t0 LEFT JOIN + (t1 + LEFT JOIN t2 ON cond1(t1,t2) + LEFT JOIN t3 ON cond2(..., possibly-t2) (*) + LEFT JOIN t4 ON cond3(..., possibly-t2, possibly-t3) + ) ON cond4 + + Suppose we're looking at whether we can eliminate outer join marked with + (*), in other words, table t3. Before we can do that, we need to + 1. Check that there are no references to table t3 in cond4 (in general: + all ON expressions of embedding outer joins, this explains the need for + descent) + 2. Check that there are no references to table t3 in its following-siblings, + in this example, in cond3. + 3. Although SQL language doesn't allow referring to table t3 from cond1, + simplify_joins() may create such back-references, so we'll also need to + check if t3's preceding-siblings have ON expressions with references + from t3. + + ASCENT AND THE ELIMINATION + The removal is done in a bottom-up way because we can consider an outer + join nest for elimination only after we have successfully eliminated all + of its children outer joins. + + RETURN + Number of tables that have been eliminated */ static int eliminate_tables_for_join_list(JOIN *join, List *join_list, - table_map used_tables_elsewhere, + table_map tables_used_elsewhere, uint *const_tbl_count, table_map *const_tables) { List_iterator it(*join_list); - table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca - table_map used_tables_on_left; + table_map used_tables_on_right[MAX_TABLES]; + table_map tables_used_on_left; TABLE_LIST *tbl; int i, n_tables; int eliminated=0; - /* Collect the reverse-bitmap-array */ + /* Collect used_tables_on_right array */ for (i=0; (tbl= it++); i++) { used_tables_on_right[i]= 0; @@ -201,20 +226,18 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, used_tables_on_right[i]= tbl->nested_join->used_tables; } n_tables= i; - for (i= n_tables - 2; i > 0; i--) used_tables_on_right[i] |= used_tables_on_right[i+1]; - it.rewind(); - - /* Walk through tables and join nests and see if we can eliminate them */ - used_tables_on_left= 0; i= 1; + it.rewind(); + tables_used_on_left= 0; + /* For each member of the join list, check if we can eliminate it */ while ((tbl= it++)) { - table_map tables_used_outside= used_tables_on_left | + table_map tables_used_outside= tables_used_on_left | used_tables_on_right[i] | - used_tables_elsewhere; + tables_used_elsewhere; table_map cur_tables= 0; if (tbl->nested_join) @@ -293,7 +316,6 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, else if (tbl->on_expr) { cur_tables= tbl->on_expr->used_tables(); - /* Check and remove */ if (!(tbl->table->map & tables_used_outside) && table_has_one_match(tbl->table, (table_map)-1)) { @@ -304,9 +326,8 @@ eliminate_tables_for_join_list(JOIN *join, List *join_list, } } - /* Update bitmap of tables we've seen on the left */ i++; - used_tables_on_left |= cur_tables; + tables_used_on_left |= cur_tables; } return eliminated; } @@ -337,7 +358,7 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, /* - Check the table will produce at most one matching record + Check if the table will produce at most one matching record SYNOPSIS table_has_one_match() @@ -345,8 +366,23 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, bound_tables Tables that should be considered bound. DESCRIPTION - Check if the given table will produce at most one matching record for - each record combination of tables in bound_tables. + Check if table will produce at most one matching record for each record + combination of tables in bound_tables bitmap. + + The check is based on ref analysis data, KEYUSE structures. We're + handling two cases: + + 1. Table has a UNIQUE KEY(uk_col_1, ... uk_col_N), and for each uk_col_i + there is a KEYUSE that represents a limitation in form + + table.uk_col_i = func(bound_tables) (X) + + 2. Same as above but we also handle limitations in form + + table.uk_col_i = func(bound_tables, uk_col_j1, ... uk_col_j2) (XX) + + where values of uk_col_jN are known to be bound because for them we + have an equality of form (X) or (XX). RETURN TRUE Yes, at most one match @@ -358,13 +394,6 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables) KEYUSE *keyuse= table->reginfo.join_tab->keyuse; if (keyuse) { - /* - Walk through all of the KEYUSE elements and - - locate unique keys - - check if we have eq_ref access for them - TODO any other reqs? - loops are constructed like in best_access_path - */ while (keyuse->table == table) { uint key= keyuse->key; @@ -415,14 +444,17 @@ typedef struct st_keyuse_w_needed_reg /* + Check if KEYUSE elemements with unusable==TRUE bind all parts of the key + SYNOPSIS + extra_keyuses_bind_all_keyparts() bound_tables Tables which can be considered constants table Table we're examining key_start Start of KEYUSE array with elements describing the key of interest key_end End of the array + 1 - n_keyuses Number + n_keyuses Number of elements in the array that have unusable==TRUE bound_parts Key parts whose values are known to be bound. DESCRIPTION @@ -442,6 +474,10 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, KEYUSE *key_start, KEYUSE *key_end, uint n_keyuses, table_map bound_parts) { + /* + Current implementation needs some keyparts to be already bound to start + inferences: + */ if (n_keyuses && bound_parts) { KEY *keyinfo= table->key_info + key_start->key; @@ -450,7 +486,8 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, n_keyuses))) return FALSE; uint n_uses=0; - /* First, collect an array*/ + + /* First, collect an array */ for (KEYUSE *k= key_start; k!=key_end; k++) { if (!k->usable && !(k->used_tables & ~bound_tables)) @@ -466,14 +503,17 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, } } - /* Now compute transitive closure */ + /* + Now, repeatedly walk through the and + see if we can find an elements that depend only on bound parts and + hence make one more part bound. + */ uint n_bounded; do { n_bounded= 0; for (uint i=0; i< n_uses; i++) { - /* needed_parts is covered by what is already bound*/ if (!(uses[i].dependency_parts & ~bound_parts)) { bound_parts|= key_part_map(1) << uses[i].keyuse->keypart; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4d85570b6d2..2d9b1766c4f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -16699,10 +16699,11 @@ static void print_join(THD *thd, { TABLE_LIST *curr= *tbl; /* - The (*) check guards againist the case of printing the query for - CREATE VIEW. There we'll have nested_join->used_tables==0. + The "eliminated_tables &&" check guards againist the case of + printing the query for CREATE VIEW. We do that without having run + JOIN::optimize() and so will have nested_join->used_tables==0. */ - if (eliminated_tables && // (*) + if (eliminated_tables && ((curr->table && (curr->table->map & eliminated_tables)) || (curr->nested_join && !(curr->nested_join->used_tables & ~eliminated_tables)))) diff --git a/sql/sql_select.h b/sql/sql_select.h index 9bce799b02d..b2f61337ab4 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -300,7 +300,8 @@ public: */ bool resume_nested_loop; table_map const_table_map,found_const_table_map; - + + /* Tables removed by table elimination. Set to 0 before the elimination. */ table_map eliminated_tables; /* Bitmap of all inner tables from outer joins From 9fa1bce4366ee6c8266395b66debc6110c090087 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 29 Jun 2009 17:51:15 +0400 Subject: [PATCH 17/27] MWL#17: Table elimination mysql-test/r/table_elim.result: MWL#17: Table elimination - More tests mysql-test/t/table_elim.test: MWL#17: Table elimination - More tests sql/opt_table_elimination.cc: MWL#17: Table elimination - Code cleanup sql/sql_select.cc: MWL#17: Table elimination - Code cleanup sql/sql_select.h: MWL#17: Table elimination - Code cleanup sql/table.h: MWL#17: Table elimination - Code cleanup --- mysql-test/r/table_elim.result | 30 +++ mysql-test/t/table_elim.test | 33 ++++ sql/opt_table_elimination.cc | 349 +++++++++++++++------------------ sql/sql_select.cc | 56 ++++-- sql/sql_select.h | 20 +- sql/table.h | 7 +- 6 files changed, 277 insertions(+), 218 deletions(-) diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result index bd35fc3de33..f635429ba8e 100644 --- a/mysql-test/r/table_elim.result +++ b/mysql-test/r/table_elim.result @@ -172,3 +172,33 @@ Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1 Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t2` `A2` where ((`F`.`id` = `A2`.`id`) and (`A2`.`attr2` between 12 and 14) and (`A2`.`fromdate` = (select max(`test`.`t2`.`fromdate`) AS `MAX(fromdate)` from `test`.`t2` where (`test`.`t2`.`id` = `F`.`id`)))) drop view v1, v2; drop table t0, t1, t2; +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); +create table t2 (pk1 int, pk2 int, pk3 int, col int, primary key(pk1, pk2, pk3)); +insert into t2 select a,a,a,a from t1; +This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and +t2.pk2=t2.pk1+1 and +t2.pk3=t2.pk2+1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and +t2.pk3=t2.pk1+1 and +t2.pk2=t2.pk3+1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +This must use both: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and +t2.pk3=t2.pk1+1 and +t2.pk2=t2.pk3+t2.col; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +1 SIMPLE t2 ref PRIMARY PRIMARY 4 test.t1.a 1 +This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk2=t1.a and +t2.pk1=t2.pk2+1 and +t2.pk3=t2.pk1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 +drop table t1, t2; diff --git a/mysql-test/t/table_elim.test b/mysql-test/t/table_elim.test index 807fd736bf6..5e6126b9ede 100644 --- a/mysql-test/t/table_elim.test +++ b/mysql-test/t/table_elim.test @@ -125,3 +125,36 @@ explain extended select id from v2 where attr2 between 12 and 14; drop view v1, v2; drop table t0, t1, t2; + +# +# Tests for the code that uses t.keypartX=func(t.keypartY) equalities to +# make table elimination inferences +# +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); + +create table t2 (pk1 int, pk2 int, pk3 int, col int, primary key(pk1, pk2, pk3)); +insert into t2 select a,a,a,a from t1; + +--echo This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and + t2.pk2=t2.pk1+1 and + t2.pk3=t2.pk2+1; + +--echo This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and + t2.pk3=t2.pk1+1 and + t2.pk2=t2.pk3+1; + +--echo This must use both: +explain select t1.* from t1 left join t2 on t2.pk1=t1.a and + t2.pk3=t2.pk1+1 and + t2.pk2=t2.pk3+t2.col; + +--echo This must use only t1: +explain select t1.* from t1 left join t2 on t2.pk2=t1.a and + t2.pk1=t2.pk2+1 and + t2.pk3=t2.pk1; + +drop table t1, t2; + diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 26ebb2232c4..4da061d0e60 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -42,14 +42,16 @@ Table elimination is redone on every PS re-execution. */ -static int -eliminate_tables_for_join_list(JOIN *join, List *join_list, - table_map tables_used_elsewhere, - uint *const_tbl_count, table_map *const_tables); -static bool table_has_one_match(TABLE *table, table_map bound_tables); -static void -mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, - table_map *const_tables); +static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl); +static bool table_has_one_match(TABLE *table, table_map bound_tables, + bool *multiple_matches); +static uint +eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, + List *join_list, + bool its_outer_join, + table_map tables_in_list, + table_map tables_used_elsewhere, + bool *multiple_matches); static bool extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, KEYUSE *key_start, KEYUSE *key_end, @@ -95,8 +97,7 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, */ -void eliminate_tables(JOIN *join, uint *const_tbl_count, - table_map *const_tables) +void eliminate_tables(JOIN *join) { Item *item; table_map used_tables; @@ -139,221 +140,141 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count, used_tables |= item->used_tables(); } } - - if (((1 << join->tables) - 1) & ~used_tables) + + table_map all_tables= join->all_tables_map(); + if (all_tables & ~used_tables) { /* There are some tables that we probably could eliminate. Try it. */ - eliminate_tables_for_join_list(join, join->join_list, used_tables, - const_tbl_count, const_tables); + TABLE *leaves_array[MAX_TABLES]; + bool multiple_matches= FALSE; + eliminate_tables_for_list(join, leaves_array, join->join_list, FALSE, + all_tables, used_tables, &multiple_matches); } DBUG_VOID_RETURN; } - - - /* Perform table elimination in a given join list SYNOPSIS - eliminate_tables_for_join_list() + eliminate_tables_for_list() join The join join_list Join list to work on tables_used_elsewhere Bitmap of tables that are referred to from somewhere outside of the join list (e.g. select list, HAVING, etc). - const_tbl_count INOUT Number of constant tables (eliminated tables - are considered constant) - const_tables INOUT Bitmap of constant tables. - DESCRIPTION - Try eliminating members of the given join list (and its children, - recursively). - - Search for tables to be eliminated is performed on recursive descent, - while the elimination is done on ascent. - - DESCENT AND NO-REFERENCES CHECK - The descent part is needed because of the following: consider a join list - - t0 LEFT JOIN - (t1 - LEFT JOIN t2 ON cond1(t1,t2) - LEFT JOIN t3 ON cond2(..., possibly-t2) (*) - LEFT JOIN t4 ON cond3(..., possibly-t2, possibly-t3) - ) ON cond4 - - Suppose we're looking at whether we can eliminate outer join marked with - (*), in other words, table t3. Before we can do that, we need to - 1. Check that there are no references to table t3 in cond4 (in general: - all ON expressions of embedding outer joins, this explains the need for - descent) - 2. Check that there are no references to table t3 in its following-siblings, - in this example, in cond3. - 3. Although SQL language doesn't allow referring to table t3 from cond1, - simplify_joins() may create such back-references, so we'll also need to - check if t3's preceding-siblings have ON expressions with references - from t3. - - ASCENT AND THE ELIMINATION - The removal is done in a bottom-up way because we can consider an outer - join nest for elimination only after we have successfully eliminated all - of its children outer joins. RETURN - Number of tables that have been eliminated + Number of base tables left after elimination. 0 means everything was + eliminated. Tables that belong to the + children of this join nest are also counted. + +// TRUE The entire join list can be eliminated (caller should remove) +// FALSE Otherwise + number of tables that were eliminated (compare this with total number of + tables in the join_list to tell if the entire join was eliminated) */ - -static int -eliminate_tables_for_join_list(JOIN *join, List *join_list, - table_map tables_used_elsewhere, - uint *const_tbl_count, table_map *const_tables) +static uint +eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, + List *join_list, + bool its_outer_join, + table_map tables_in_list, + table_map tables_used_elsewhere, + bool *multiple_matches) { - List_iterator it(*join_list); - table_map used_tables_on_right[MAX_TABLES]; - table_map tables_used_on_left; TABLE_LIST *tbl; - int i, n_tables; - int eliminated=0; + List_iterator it(*join_list); + table_map tables_used_on_left= 0; + TABLE **cur_table= leaves_arr; + bool children_have_multiple_matches= FALSE; + uint base_tables= 0; - /* Collect used_tables_on_right array */ - for (i=0; (tbl= it++); i++) - { - used_tables_on_right[i]= 0; - if (tbl->on_expr) - used_tables_on_right[i]= tbl->on_expr->used_tables(); - if (tbl->nested_join) - used_tables_on_right[i]= tbl->nested_join->used_tables; - } - n_tables= i; - for (i= n_tables - 2; i > 0; i--) - used_tables_on_right[i] |= used_tables_on_right[i+1]; - - i= 1; - it.rewind(); - tables_used_on_left= 0; - /* For each member of the join list, check if we can eliminate it */ while ((tbl= it++)) { - table_map tables_used_outside= tables_used_on_left | - used_tables_on_right[i] | - tables_used_elsewhere; - table_map cur_tables= 0; - - if (tbl->nested_join) + if (tbl->on_expr) { - DBUG_ASSERT(tbl->on_expr); - /* - There can be cases where table removal is applicable for tables - within the outer join but not for the outer join itself. Ask to - remove the children first. - - TODO: NoHopelessEliminationAttempts: the below call can return - information about whether it would make any sense to try removing - this entire outer join nest. - */ - int eliminated_in_children= - eliminate_tables_for_join_list(join, &tbl->nested_join->join_list, - tables_used_outside, - const_tbl_count, const_tables); - tbl->nested_join->n_tables -=eliminated_in_children; - cur_tables= tbl->nested_join->used_tables; - if (!(cur_tables & tables_used_outside)) + table_map outside_used_tables= tables_used_elsewhere | + tables_used_on_left; + bool multiple_matches= FALSE; + if (tbl->nested_join) { - /* - Check if all embedded tables together can produce at most one - record combination. This is true when - - each of them has one_match(outer-tables) property - (this is a stronger condition than all of them together having - this property but that's irrelevant here) - - there are no outer joins among them - (except for the case of outer join which has all inner tables - to be constant and is guaranteed to produce only one record. - that record will be null-complemented) - */ - bool one_match= TRUE; - List_iterator it2(tbl->nested_join->join_list); - TABLE_LIST *inner; - while ((inner= it2++)) + /* This is "... LEFT JOIN (join_nest) ON cond" */ + uint n; + if (!(n= eliminate_tables_for_list(join, cur_table, + &tbl->nested_join->join_list, TRUE, + tbl->nested_join->used_tables, + outside_used_tables, + &multiple_matches))) { - /* - Bail out if we see an outer join (TODO: handle the above - null-complemntated-rows-only case) - */ - if (inner->on_expr) - { - one_match= FALSE; - break; - } - - if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts - !table_has_one_match(inner->table, - ~tbl->nested_join->used_tables)) - { - one_match= FALSE; - break; - } - } - if (one_match) - { - it2.rewind(); - while ((inner= it2++)) - { - mark_table_as_eliminated(join, inner->table, const_tbl_count, - const_tables); - } - eliminated += tbl->nested_join->join_list.elements; - //psergey-todo: do we need to do anything about removing the join - //nest? - tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); - } - else - { - eliminated += eliminated_in_children; + mark_as_eliminated(join, tbl); } + tbl->nested_join->n_tables= n; + base_tables += n; } + else + { + /* This is "... LEFT JOIN tbl ON cond" */ + if (!(tbl->table->map & outside_used_tables) && + table_has_one_match(tbl->table, join->all_tables_map(), + &multiple_matches)) + { + mark_as_eliminated(join, tbl); + } + else + base_tables++; + } + tables_used_on_left |= tbl->on_expr->used_tables(); + children_have_multiple_matches= children_have_multiple_matches || + multiple_matches; } - else if (tbl->on_expr) + else { - cur_tables= tbl->on_expr->used_tables(); - if (!(tbl->table->map & tables_used_outside) && - table_has_one_match(tbl->table, (table_map)-1)) - { - mark_table_as_eliminated(join, tbl->table, const_tbl_count, - const_tables); - tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); - eliminated += 1; - } + DBUG_ASSERT(!tbl->nested_join); + base_tables++; } - i++; - tables_used_on_left |= cur_tables; + if (tbl->table) + *(cur_table++)= tbl->table; } - return eliminated; -} - -/* - Mark table as eliminated: - - Mark it as constant table - - Move it to the front of join order - - Record it in join->eliminated_tables -*/ - -static -void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, - table_map *const_tables) -{ - JOIN_TAB *tab= table->reginfo.join_tab; - if (!(*const_tables & tab->table->map)) + *multiple_matches |= children_have_multiple_matches; + + /* Try eliminating the nest we're called for */ + if (its_outer_join && !children_have_multiple_matches && + !(tables_in_list & tables_used_elsewhere)) { - DBUG_PRINT("info", ("Eliminated table %s", table->alias)); - tab->type= JT_CONST; - join->eliminated_tables |= table->map; - *const_tables |= table->map; - join->const_table_map|= table->map; - set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); + table_map bound_tables= join->const_table_map | (join->all_tables_map() & + ~tables_in_list); + table_map old_bound_tables; + TABLE **leaves_end= cur_table; + /* + Do the same as const table search table: try to expand the set of bound + tables until it covers all tables in the join_list + */ + do + { + old_bound_tables= bound_tables; + for (cur_table= leaves_arr; cur_table != leaves_end; cur_table++) + { + if (!((*cur_table)->map & join->eliminated_tables) && + table_has_one_match(*cur_table, bound_tables, multiple_matches)) + { + bound_tables |= (*cur_table)->map; + } + } + } while (old_bound_tables != bound_tables); + + if (!(tables_in_list & ~bound_tables)) + { + /* + This join_list can be eliminated. Signal about this to the caller by + returning number of tables. + */ + base_tables= 0; + } } + return base_tables; } @@ -362,8 +283,11 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, SYNOPSIS table_has_one_match() - table The [base] table being checked - bound_tables Tables that should be considered bound. + table The [base] table being checked + bound_tables Tables that should be considered bound. + multiple_matches OUT Set to TRUE when there is no way we could + find find a limitation that would give us one-match + property. DESCRIPTION Check if table will produce at most one matching record for each record @@ -389,7 +313,8 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, FALSE No */ -static bool table_has_one_match(TABLE *table, table_map bound_tables) +static bool table_has_one_match(TABLE *table, table_map bound_tables, + bool *multiple_matches) { KEYUSE *keyuse= table->reginfo.join_tab->keyuse; if (keyuse) @@ -405,7 +330,7 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables) do /* For each keypart and each way to read it */ { - if (keyuse->usable) + if (keyuse->usable == 1) { if(!(keyuse->used_tables & ~bound_tables) && !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) @@ -516,8 +441,10 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, { if (!(uses[i].dependency_parts & ~bound_parts)) { + table_map old= bound_parts; bound_parts|= key_part_map(1) << uses[i].keyuse->keypart; - n_bounded++; + if (old != bound_parts) + n_bounded++; } if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) return TRUE; @@ -527,6 +454,42 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, return FALSE; } + +/* + Mark one table or the whole join nest as eliminated. +*/ +static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl) +{ + TABLE *table; + /* + NOTE: there are TABLE_LIST object that have + tbl->table!= NULL && tbl->nested_join!=NULL and + tbl->table == tbl->nested_join->join_list->element(..)->table + */ + if (tbl->nested_join) + { + TABLE_LIST *child; + List_iterator it(tbl->nested_join->join_list); + while ((child= it++)) + mark_as_eliminated(join, child); + } + else if ((table= tbl->table)) + { + JOIN_TAB *tab= tbl->table->reginfo.join_tab; + if (!(join->const_table_map & tab->table->map)) + { + DBUG_PRINT("info", ("Eliminated table %s", table->alias)); + tab->type= JT_CONST; + join->eliminated_tables |= table->map; + join->const_table_map|= table->map; + set_position(join, join->const_tables++, tab, (KEYUSE*)0); + } + } + + if (tbl->on_expr) + tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); +} + /** @} (end of group Table_Elimination) */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2d9b1766c4f..729ee190a0f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2653,12 +2653,13 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, ~outer_join, join->select_lex, &sargables)) goto error; - /* Read tables with 0 or 1 rows (system tables) */ join->const_table_map= 0; - - eliminate_tables(join, &const_count, &found_const_table_map); - join->const_table_map= found_const_table_map; + join->const_tables= const_count; + eliminate_tables(join); + const_count= join->const_tables; + found_const_table_map= join->const_table_map; + /* Read tables with 0 or 1 rows (system tables) */ for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count; p_pos < p_end ; p_pos++) @@ -2761,7 +2762,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, { start_keyuse=keyuse; key=keyuse->key; - if (keyuse->usable) + if (keyuse->usable == 1) s->keys.set_bit(key); // QQ: remove this ? refs=0; @@ -2769,7 +2770,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, eq_part.clear_all(); do { - if (keyuse->usable && keyuse->val->type() != Item::NULL_ITEM && + if (keyuse->usable==1 && keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) { if (!((~found_const_table_map) & keyuse->used_tables)) @@ -3601,7 +3602,12 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.null_rejecting= key_field->null_rejecting; keyuse.cond_guard= key_field->cond_guard; - keyuse.usable= key_field->usable; + if (!(keyuse.usable= key_field->usable)) + { + /* The following will have special meanings: */ + keyuse.keypart_map= 0; + keyuse.used_tables= 0; + } VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } } @@ -3668,7 +3674,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, keyuse.used_tables=cond_func->key_item()->used_tables(); keyuse.optimize= 0; keyuse.keypart_map= 0; - keyuse.usable= TRUE; + keyuse.usable= 1; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } @@ -3686,7 +3692,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) // Usable ones go before the unusable if (a->usable != b->usable) - return (int)a->usable - (int)b->usable; + return (int)b->usable - (int)a->usable; // Place const values before other ones if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - @@ -3898,7 +3904,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, found_eq_constant=0; for (i=0 ; i < keyuse->elements-1 ; i++,use++) { - if (use->usable && !use->used_tables && + if (use->usable == 1 && !use->used_tables && use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->table->const_key_parts[use->key]|= use->keypart_map; if (use->keypart != FT_KEYPART) @@ -3923,7 +3929,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, /* Save ptr to first use */ if (!use->table->reginfo.join_tab->keyuse) use->table->reginfo.join_tab->keyuse=save_pos; - if (use->usable) + if (use->usable == 1) use->table->reginfo.join_tab->checked_keys.set_bit(use->key); save_pos++; } @@ -3954,7 +3960,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) To avoid bad matches, we don't make ref_table_rows less than 100. */ keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref - if (keyuse->usable && keyuse->used_tables & + if (keyuse->usable == 1 && keyuse->used_tables & (map= (keyuse->used_tables & ~join->const_table_map & ~OUTER_REF_TABLE_BIT))) { @@ -4146,7 +4152,7 @@ best_access_path(JOIN *join, if 1. expression doesn't refer to forward tables 2. we won't get two ref-or-null's */ - if (keyuse->usable && + if (keyuse->usable == 1&& !(remaining_tables & keyuse->used_tables) && !(ref_or_null_part && (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))) @@ -5601,7 +5607,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, */ do { - if (!(~used_tables & keyuse->used_tables)) + if (!(~used_tables & keyuse->used_tables) && keyuse->usable == 1) { if (keyparts == keyuse->keypart && !(found_part_ref_or_null & keyuse->optimize)) @@ -5652,7 +5658,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, for (i=0 ; i < keyparts ; keyuse++,i++) { while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) || - !keyuse->usable) + !(keyuse->usable == 1)) { keyuse++; /* Skip other parts */ } @@ -8985,6 +8991,20 @@ static void restore_prev_nj_state(JOIN_TAB *last) JOIN *join= last->join; while (last_emb) { + /* + psergey-elim: (nevermind) + new_prefix= cur_prefix & ~last; + if (!(new_prefix & cur_table_map)) // removed last inner table + { + join->cur_embedding_map&= ~last_emb->nested_join->nj_map; + } + else (current) + { + // Won't hurt doing it all the time: + join->cur_embedding_map |= ...; + } + else + */ if (!(--last_emb->nested_join->counter)) join->cur_embedding_map&= ~last_emb->nested_join->nj_map; else if (last_emb->nested_join->n_tables-1 == @@ -16685,13 +16705,13 @@ static void print_join(THD *thd, DBUG_ASSERT(tables->elements >= 1); /* - Assert that the first table in the list isn't eliminated (if it was we - would have skipped the entire join nest) + Assert that the first table in the list isn't eliminated. This comes from + the fact that the first table can't be inner table of an outer join. */ DBUG_ASSERT(!eliminated_tables || !((*table)->table && ((*table)->table->map & eliminated_tables) || (*table)->nested_join && !((*table)->nested_join->used_tables & - ~eliminated_tables))); + ~eliminated_tables))); (*table)->print(thd, eliminated_tables, str, query_type); TABLE_LIST **end= table + tables->elements; diff --git a/sql/sql_select.h b/sql/sql_select.h index b2f61337ab4..ae334a2f012 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -57,14 +57,14 @@ typedef struct keyuse_t { */ bool *cond_guard; /* - TRUE <=> This keyuse can be used to construct key access. - FALSE <=> Otherwise. Currently unusable KEYUSEs represent equalities + 1 <=> This keyuse can be used to construct key access. + 0 <=> Otherwise. Currently unusable KEYUSEs represent equalities where one table column refers to another one, like this: t.keyXpartA=func(t.keyXpartB) This equality cannot be used for index access but is useful for table elimination. */ - bool usable; + int usable; } KEYUSE; class store_key; @@ -299,7 +299,12 @@ public: fetching data from a cursor */ bool resume_nested_loop; - table_map const_table_map,found_const_table_map; + table_map const_table_map; + /* + Constant tables for which we have found a row (as opposed to those for + which we didn't). + */ + table_map found_const_table_map; /* Tables removed by table elimination. Set to 0 before the elimination. */ table_map eliminated_tables; @@ -548,6 +553,10 @@ public: return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 || select_lex == unit->fake_select_lex)); } + inline table_map all_tables_map() + { + return (table_map(1) << tables) - 1; + } private: bool make_simple_join(JOIN *join, TABLE *tmp_table); }; @@ -755,6 +764,5 @@ inline bool optimizer_flag(THD *thd, uint flag) return (thd->variables.optimizer_switch & flag); } -void eliminate_tables(JOIN *join, uint *const_tbl_count, - table_map *const_tables); +void eliminate_tables(JOIN *join); diff --git a/sql/table.h b/sql/table.h index f443d591dec..e342fb7194c 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1616,7 +1616,10 @@ public: typedef struct st_nested_join { List join_list; /* list of elements in the nested join */ - table_map used_tables; /* bitmap of tables in the nested join */ + /* + Bitmap of tables within this nested join (including those embedded within + its children). Eliminated tables are still in the bitmap */ + table_map used_tables; table_map not_null_tables; /* tables that rejects nulls */ struct st_join_table *first_nested;/* the first nested table in the plan */ /* @@ -1625,6 +1628,8 @@ typedef struct st_nested_join 2. check_interleaving_with_nj/restore_prev_nj_state (these are called by the join optimizer. Before each use the counters are zeroed by reset_nj_counters. + Meaning, in both cases: number of base tables within this nested join and + its children. Eliminated tables are not counted. */ uint counter; /* Tables left after elimination */ From 8156d9eb0a635d77c4f33b337d47e658d19fe1f6 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 30 Jun 2009 17:11:00 +0400 Subject: [PATCH 18/27] MWL#17: Table elimination - Last fixes sql/item.cc: MWL#17: Table elimination - Don't make multiple calls of ::walk(check_column_usage_processor), call once and cache the value sql/item.h: MWL#17: Table elimination - s/KEYUSE::usable/KEYUSE::type/, more comments sql/opt_table_elimination.cc: MWL#17: Table elimination - Don't make multiple calls of ::walk(check_column_usage_processor), call once and cache the value sql/sql_select.cc: MWL#17: Table elimination - s/KEYUSE::usable/KEYUSE::type/, more comments sql/sql_select.h: MWL#17: Table elimination - s/KEYUSE::usable/KEYUSE::type/, more comments sql/table.h: MWL#17: Table elimination - Better comments --- sql/item.cc | 15 +++----- sql/item.h | 11 ++++-- sql/opt_table_elimination.cc | 73 ++++++++++++++++++++++++++++-------- sql/sql_select.cc | 58 +++++++++++++--------------- sql/sql_select.h | 36 +++++++++++++++++- sql/table.h | 10 +++-- 6 files changed, 138 insertions(+), 65 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index f1511fdf321..a010b98cd40 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1920,10 +1920,6 @@ bool Item_field::check_column_usage_processor(uchar *arg) { Field_processor_info* info=(Field_processor_info*)arg; - /* It is ok if this is a column of an allowed table: */ - if (used_tables() & ~info->allowed_tables) - return FALSE; - if (field->table == info->table) { /* It is not ok to use columns that are not part of the key of interest: */ @@ -1936,18 +1932,17 @@ bool Item_field::check_column_usage_processor(uchar *arg) { if (field->field_index == key->key_part[part].field->field_index) { + if (part == info->forbidden_part) + return TRUE; info->needed_key_parts |= key_part_map(1) << part; break; } } return FALSE; } - - /* - We get here when this refers to a table that's neither the table of - interest, nor one of the allowed tables. - */ - return TRUE; + else + info->used_tables |= this->used_tables(); + return FALSE; } diff --git a/sql/item.h b/sql/item.h index 6f0bf02bdae..0e74c742d99 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1018,11 +1018,14 @@ public: }; /* Data for Item::check_column_usage_processor */ -typedef struct +typedef struct { - table_map allowed_tables; - TABLE *table; - uint keyno; + TABLE *table; /* Table of interest */ + uint keyno; /* Index of interest */ + uint forbidden_part; /* key part which one is not allowed to refer to */ + /* [Set by processor] used tables, besides the table of interest */ + table_map used_tables; + /* [Set by processor] Parts of index of interest that expression refers to */ uint needed_key_parts; } Field_processor_info; diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 4da061d0e60..11868b6160d 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -166,9 +166,8 @@ void eliminate_tables(JOIN *join) DESCRIPTION RETURN - Number of base tables left after elimination. 0 means everything was - eliminated. Tables that belong to the - children of this join nest are also counted. + Number of children left after elimination. 0 means everything was + eliminated. // TRUE The entire join list can be eliminated (caller should remove) // FALSE Otherwise @@ -188,7 +187,7 @@ eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, table_map tables_used_on_left= 0; TABLE **cur_table= leaves_arr; bool children_have_multiple_matches= FALSE; - uint base_tables= 0; + uint remaining_children= 0; while ((tbl= it++)) { @@ -209,8 +208,9 @@ eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, { mark_as_eliminated(join, tbl); } + else + remaining_children++; tbl->nested_join->n_tables= n; - base_tables += n; } else { @@ -222,7 +222,7 @@ eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, mark_as_eliminated(join, tbl); } else - base_tables++; + remaining_children++; } tables_used_on_left |= tbl->on_expr->used_tables(); children_have_multiple_matches= children_have_multiple_matches || @@ -231,7 +231,7 @@ eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, else { DBUG_ASSERT(!tbl->nested_join); - base_tables++; + remaining_children++; } if (tbl->table) @@ -271,10 +271,10 @@ eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, This join_list can be eliminated. Signal about this to the caller by returning number of tables. */ - base_tables= 0; + remaining_children= 0; } } - return base_tables; + return remaining_children; } @@ -330,7 +330,7 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables, do /* For each keypart and each way to read it */ { - if (keyuse->usable == 1) + if (keyuse->type == KEYUSE_USABLE) { if(!(keyuse->used_tables & ~bound_tables) && !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) @@ -400,12 +400,56 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, uint n_keyuses, table_map bound_parts) { /* - Current implementation needs some keyparts to be already bound to start - inferences: + We need + - some 'unusable' KEYUSE elements to work on + - some keyparts to be already bound to start inferences: */ if (n_keyuses && bound_parts) { - KEY *keyinfo= table->key_info + key_start->key; + KEY *keyinfo= table->key_info + key_start->key; + bool bound_more_parts; + do + { + bound_more_parts= FALSE; + for (KEYUSE *k= key_start; k!=key_end; k++) + { + if (k->type == KEYUSE_UNKNOWN) + { + Field_processor_info fp= {table, k->key, k->keypart, 0, 0}; + if (k->val->walk(&Item::check_column_usage_processor, FALSE, + (uchar*)&fp)) + k->type= KEYUSE_NO_BIND; + else + { + k->used_tables= fp.used_tables; + k->keypart_map= fp.needed_key_parts; + k->type= KEYUSE_BIND; + } + } + + if (k->type == KEYUSE_BIND) + { + /* + If this is a binding keyuse, such that + - all tables it refers to are bound, + - all parts it refers to are bound + - but the key part it binds is not itself bound + */ + if (!(k->used_tables & ~bound_tables) && + !(k->keypart_map & ~bound_parts) && + !(bound_parts & key_part_map(1) << k->keypart)) + { + bound_parts|= key_part_map(1) << k->keypart; + if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) + return TRUE; + bound_more_parts= TRUE; + } + } + } + } while (bound_more_parts); + } + return FALSE; +#if 0 Keyuse_w_needed_reg *uses; if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)* n_keyuses))) @@ -450,8 +494,7 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, return TRUE; } } while (n_bounded != 0); - } - return FALSE; +#endif } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 729ee190a0f..b5771bc0d42 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2762,7 +2762,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, { start_keyuse=keyuse; key=keyuse->key; - if (keyuse->usable == 1) + if (keyuse->type == KEYUSE_USABLE) s->keys.set_bit(key); // QQ: remove this ? refs=0; @@ -2770,8 +2770,8 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, eq_part.clear_all(); do { - if (keyuse->usable==1 && keyuse->val->type() != Item::NULL_ITEM && - !keyuse->optimize) + if (keyuse->type == KEYUSE_USABLE && + keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) { if (!((~found_const_table_map) & keyuse->used_tables)) const_ref.set_bit(keyuse->keypart); @@ -2971,7 +2971,7 @@ typedef struct key_field_t { */ bool null_rejecting; bool *cond_guard; /* See KEYUSE::cond_guard */ - bool usable; /* See KEYUSE::usable */ + enum keyuse_type type; /* See KEYUSE::type */ } KEY_FIELD; @@ -3069,7 +3069,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, be, too (TODO: shouldn't that apply to the above null_rejecting and optimize attributes?) */ - DBUG_ASSERT(old->usable == new_fields->usable); + DBUG_ASSERT(old->type == new_fields->type); } } else if (old->eq_func && new_fields->eq_func && @@ -3085,12 +3085,13 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, old->null_rejecting= (old->null_rejecting && new_fields->null_rejecting); // "t.key_col=const" predicates are always usable - DBUG_ASSERT(old->usable && new_fields->usable); + DBUG_ASSERT(old->type == KEYUSE_USABLE && + new_fields->type == KEYUSE_USABLE); } else if (old->eq_func && new_fields->eq_func && - ((new_fields->usable && old->val->const_item() && - old->val->is_null()) || - ((old->usable && new_fields->val->is_null())))) + ((new_fields->type == KEYUSE_USABLE && + old->val->const_item() && old->val->is_null()) || + ((old->type == KEYUSE_USABLE && new_fields->val->is_null())))) /* TODO ^ why is the above asymmetric, why const_item()? */ { /* field = expression OR field IS NULL */ @@ -3181,9 +3182,6 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, if (!((value[i])->used_tables() & (field->table->map | RAND_TABLE_BIT))) optimizable=1; } - // psergey-tbl-elim: - // if (!optimizable) - // return; if (!(usable_tables & field->table->map)) { if (!eq_func || (*value)->type() != Item::NULL_ITEM || @@ -3290,7 +3288,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, (*key_fields)->val= *value; (*key_fields)->level= and_level; (*key_fields)->optimize= exists_optimize; - (*key_fields)->usable= optimizable; + (*key_fields)->type= optimizable? KEYUSE_USABLE : KEYUSE_UNKNOWN; /* If the condition has form "tbl.keypart = othertbl.field" and othertbl.field can be NULL, there will be no matches if othertbl.field @@ -3602,12 +3600,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.null_rejecting= key_field->null_rejecting; keyuse.cond_guard= key_field->cond_guard; - if (!(keyuse.usable= key_field->usable)) - { - /* The following will have special meanings: */ - keyuse.keypart_map= 0; - keyuse.used_tables= 0; - } + keyuse.type= key_field->type; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } } @@ -3674,7 +3667,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, keyuse.used_tables=cond_func->key_item()->used_tables(); keyuse.optimize= 0; keyuse.keypart_map= 0; - keyuse.usable= 1; + keyuse.type= KEYUSE_USABLE; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } @@ -3691,8 +3684,10 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) return (int) (a->keypart - b->keypart); // Usable ones go before the unusable - if (a->usable != b->usable) - return (int)b->usable - (int)a->usable; + int a_ok= test(a->type == KEYUSE_USABLE); + int b_ok= test(b->type == KEYUSE_USABLE); + if (a_ok != b_ok) + return a_ok? -1 : 1; // Place const values before other ones if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - @@ -3904,7 +3899,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, found_eq_constant=0; for (i=0 ; i < keyuse->elements-1 ; i++,use++) { - if (use->usable == 1 && !use->used_tables && + if (use->type == KEYUSE_USABLE && !use->used_tables && use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->table->const_key_parts[use->key]|= use->keypart_map; if (use->keypart != FT_KEYPART) @@ -3929,7 +3924,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, /* Save ptr to first use */ if (!use->table->reginfo.join_tab->keyuse) use->table->reginfo.join_tab->keyuse=save_pos; - if (use->usable == 1) + if (use->type == KEYUSE_USABLE) use->table->reginfo.join_tab->checked_keys.set_bit(use->key); save_pos++; } @@ -3960,7 +3955,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) To avoid bad matches, we don't make ref_table_rows less than 100. */ keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref - if (keyuse->usable == 1 && keyuse->used_tables & + if (keyuse->type == KEYUSE_USABLE && keyuse->used_tables & (map= (keyuse->used_tables & ~join->const_table_map & ~OUTER_REF_TABLE_BIT))) { @@ -4152,7 +4147,7 @@ best_access_path(JOIN *join, if 1. expression doesn't refer to forward tables 2. we won't get two ref-or-null's */ - if (keyuse->usable == 1&& + if (keyuse->type == KEYUSE_USABLE && !(remaining_tables & keyuse->used_tables) && !(ref_or_null_part && (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))) @@ -5607,7 +5602,8 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, */ do { - if (!(~used_tables & keyuse->used_tables) && keyuse->usable == 1) + if (!(~used_tables & keyuse->used_tables) && + keyuse->type == KEYUSE_USABLE) { if (keyparts == keyuse->keypart && !(found_part_ref_or_null & keyuse->optimize)) @@ -5658,7 +5654,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, for (i=0 ; i < keyparts ; keyuse++,i++) { while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) || - !(keyuse->usable == 1)) + !(keyuse->type == KEYUSE_USABLE)) { keyuse++; /* Skip other parts */ } @@ -16709,9 +16705,9 @@ static void print_join(THD *thd, the fact that the first table can't be inner table of an outer join. */ DBUG_ASSERT(!eliminated_tables || - !((*table)->table && ((*table)->table->map & eliminated_tables) || - (*table)->nested_join && !((*table)->nested_join->used_tables & - ~eliminated_tables))); + !(((*table)->table && ((*table)->table->map & eliminated_tables)) || + ((*table)->nested_join && !((*table)->nested_join->used_tables & + ~eliminated_tables)))); (*table)->print(thd, eliminated_tables, str, query_type); TABLE_LIST **end= table + tables->elements; diff --git a/sql/sql_select.h b/sql/sql_select.h index ae334a2f012..c3d0dacd78b 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -33,6 +33,40 @@ #define KEY_OPTIMIZE_EXISTS 1 #define KEY_OPTIMIZE_REF_OR_NULL 2 +/* KEYUSE element types */ +enum keyuse_type +{ + /* + val refers to the same table, this is either KEYUSE_BIND or KEYUSE_NO_BIND + type, we didn't determine which one yet. + */ + KEYUSE_UNKNOWN= 0, + /* + 'regular' keyuse, i.e. it represents one of the following + * t.keyXpartY = func(constants, other-tables) + * t.keyXpartY IS NULL + * t.keyXpartY = func(constants, other-tables) OR t.keyXpartY IS NULL + and can be used to construct ref acces + */ + KEYUSE_USABLE, + /* + The keyuse represents a condition in form: + + t.uniq_keyXpartY = func(other parts of uniq_keyX) + + This can't be used to construct uniq_keyX but we could use it to determine + that the table will produce at most one match. + */ + KEYUSE_BIND, + /* + Keyuse that's not usable for ref access and doesn't meet the criteria of + KEYUSE_BIND. Examples: + t.keyXpartY = func(t.keyXpartY) + t.keyXpartY = func(column of t that's not covered by keyX) + */ + KEYUSE_NO_BIND +}; + typedef struct keyuse_t { TABLE *table; Item *val; /**< or value if no field */ @@ -64,7 +98,7 @@ typedef struct keyuse_t { This equality cannot be used for index access but is useful for table elimination. */ - int usable; + enum keyuse_type type; } KEYUSE; class store_key; diff --git a/sql/table.h b/sql/table.h index e342fb7194c..d3c1542420b 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1618,7 +1618,8 @@ typedef struct st_nested_join List join_list; /* list of elements in the nested join */ /* Bitmap of tables within this nested join (including those embedded within - its children). Eliminated tables are still in the bitmap */ + its children), including tables removed by table elimination. + */ table_map used_tables; table_map not_null_tables; /* tables that rejects nulls */ struct st_join_table *first_nested;/* the first nested table in the plan */ @@ -1628,11 +1629,12 @@ typedef struct st_nested_join 2. check_interleaving_with_nj/restore_prev_nj_state (these are called by the join optimizer. Before each use the counters are zeroed by reset_nj_counters. - Meaning, in both cases: number of base tables within this nested join and - its children. Eliminated tables are not counted. */ uint counter; - /* Tables left after elimination */ + /* + Number of elements in join_list that were not (or contain table(s) that + weren't) removed by table elimination. + */ uint n_tables; nested_join_map nj_map; /* Bit used to identify this nested join*/ } NESTED_JOIN; From 7b3d46457879bdae3be97dc4ec6aea3bf96c6697 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 30 Jun 2009 17:20:18 +0400 Subject: [PATCH 19/27] MWL#17: Table elimination - More comments - Renove old code --- sql/opt_table_elimination.cc | 71 +++++++----------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 11868b6160d..fc8bf1231ea 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -159,20 +159,28 @@ void eliminate_tables(JOIN *join) SYNOPSIS eliminate_tables_for_list() join The join + leaves_arr OUT Store here an array of leaf (base) tables that + are descendants of the join_list, and increment + the pointer to point right above the array. join_list Join list to work on + its_outer_join TRUE <=> join_list is an inner side of an outer + join + FALSE <=> otherwise (this is top-level join list) + tables_in_list Bitmap of tables embedded in the join_list. tables_used_elsewhere Bitmap of tables that are referred to from somewhere outside of the join list (e.g. select list, HAVING, etc). + DESCRIPTION + Perform table elimination for a join list. + Try eliminating children nests first. + The "all tables in join nest can produce only one matching record + combination" property checking is modeled after constant table detection, + plus we reuse info attempts to eliminate child join nests. RETURN Number of children left after elimination. 0 means everything was eliminated. - -// TRUE The entire join list can be eliminated (caller should remove) -// FALSE Otherwise - number of tables that were eliminated (compare this with total number of - tables in the join_list to tell if the entire join was eliminated) */ static uint eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, @@ -361,13 +369,6 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables, } -typedef struct st_keyuse_w_needed_reg -{ - KEYUSE *keyuse; - key_part_map dependency_parts; -} Keyuse_w_needed_reg; - - /* Check if KEYUSE elemements with unusable==TRUE bind all parts of the key @@ -449,52 +450,6 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, } while (bound_more_parts); } return FALSE; -#if 0 - Keyuse_w_needed_reg *uses; - if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)* - n_keyuses))) - return FALSE; - uint n_uses=0; - - /* First, collect an array */ - for (KEYUSE *k= key_start; k!=key_end; k++) - { - if (!k->usable && !(k->used_tables & ~bound_tables)) - { - Field_processor_info fp= {bound_tables, table, k->key, 0}; - if (!k->val->walk(&Item::check_column_usage_processor, FALSE, - (uchar*)&fp)) - { - uses[n_uses].keyuse= k; - uses[n_uses].dependency_parts= fp.needed_key_parts; - n_uses++; - } - } - } - - /* - Now, repeatedly walk through the and - see if we can find an elements that depend only on bound parts and - hence make one more part bound. - */ - uint n_bounded; - do - { - n_bounded= 0; - for (uint i=0; i< n_uses; i++) - { - if (!(uses[i].dependency_parts & ~bound_parts)) - { - table_map old= bound_parts; - bound_parts|= key_part_map(1) << uses[i].keyuse->keypart; - if (old != bound_parts) - n_bounded++; - } - if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) - return TRUE; - } - } while (n_bounded != 0); -#endif } From 5ecad03df9eef6e4d68cc7767b684944384e084e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 8 Jul 2009 21:10:38 +0400 Subject: [PATCH 20/27] MWL#17: Table elimination - When collecting Item_subselect::refers_to, put references to the correct subselect entry. --- sql/sql_lex.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index dc8dadc971a..680a3851723 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1780,6 +1780,7 @@ void st_select_lex_unit::exclude_tree() void st_select_lex::mark_as_dependent(st_select_lex *last, Item *dependency) { + SELECT_LEX *next_to_last; /* Mark all selects from resolved to 1 before select where was found table as depended (of select where was found table) @@ -1787,6 +1788,7 @@ void st_select_lex::mark_as_dependent(st_select_lex *last, Item *dependency) for (SELECT_LEX *s= this; s && s != last; s= s->outer_select()) + { if (!(s->uncacheable & UNCACHEABLE_DEPENDENT)) { // Select is dependent of outer select @@ -1802,10 +1804,12 @@ void st_select_lex::mark_as_dependent(st_select_lex *last, Item *dependency) sl->uncacheable|= UNCACHEABLE_UNITED; } } + next_to_last= s; + } is_correlated= TRUE; this->master_unit()->item->is_correlated= TRUE; if (dependency) - this->master_unit()->item->refers_to.push_back(dependency); + next_to_last->master_unit()->item->refers_to.push_back(dependency); } bool st_select_lex_node::set_braces(bool value) { return 1; } From 854bb82bd81afa6decc436b9ecf6af4954efebe5 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 02:34:21 +0400 Subject: [PATCH 21/27] MWL#17: Table elimination Address review feedback: - Change from Wave-based approach (a-la const table detection) to building and walking functional dependency graph. - Change from piggy-backing on ref-access code and KEYUSE structures to using our own expression analyzer. sql/item.cc: MWL#17: Table elimination - Move from C-ish Field_processor_info to C++ ish and generic Field_enumerator sql/item.h: MWL#17: Table elimination - Move from C-ish Field_processor_info to C++ ish and generic Field_enumerator sql/sql_bitmap.h: MWL#17: Table elimination - Backport of Table_map_iterator from 6.0 --- sql/item.cc | 26 +- sql/item.h | 10 +- sql/opt_table_elimination.cc | 1392 ++++++++++++++++++++++++++-------- sql/sql_bitmap.h | 33 + 4 files changed, 1130 insertions(+), 331 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index a010b98cd40..9cf369a2670 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1918,30 +1918,8 @@ void Item_field::reset_field(Field *f) bool Item_field::check_column_usage_processor(uchar *arg) { - Field_processor_info* info=(Field_processor_info*)arg; - - if (field->table == info->table) - { - /* It is not ok to use columns that are not part of the key of interest: */ - if (!(field->part_of_key.is_set(info->keyno))) - return TRUE; - - /* Find which key part we're using and mark it in needed_key_parts */ - KEY *key= &field->table->key_info[info->keyno]; - for (uint part= 0; part < key->key_parts; part++) - { - if (field->field_index == key->key_part[part].field->field_index) - { - if (part == info->forbidden_part) - return TRUE; - info->needed_key_parts |= key_part_map(1) << part; - break; - } - } - return FALSE; - } - else - info->used_tables |= this->used_tables(); + Field_enumerator *fe= (Field_enumerator*)arg; + fe->see_field(field); return FALSE; } diff --git a/sql/item.h b/sql/item.h index 0e74c742d99..53ae8ab8826 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1017,7 +1017,7 @@ public: bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs); }; -/* Data for Item::check_column_usage_processor */ +#if 0 typedef struct { TABLE *table; /* Table of interest */ @@ -1028,7 +1028,15 @@ typedef struct /* [Set by processor] Parts of index of interest that expression refers to */ uint needed_key_parts; } Field_processor_info; +#endif +/* Data for Item::check_column_usage_processor */ +class Field_enumerator +{ +public: + virtual void see_field(Field *field)= 0; + virtual ~Field_enumerator() {}; /* Shut up compiler warning */ +}; class sp_head; diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index fc8bf1231ea..e5829b1eb96 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -13,6 +13,7 @@ #endif #include "mysql_priv.h" +#include "my_bit.h" #include "sql_select.h" /* @@ -35,27 +36,866 @@ * Items that became disused because they were in the ON expression of an eliminated outer join are notified by means of the Item tree walk which calls Item::mark_as_eliminated_processor for every item - - At the moment the only Item that cares is Item_subselect with its - Item_subselect::eliminated flag which is used by EXPLAIN code to - check if the subquery should be shown in EXPLAIN. + - At the moment the only Item that cares whether it was eliminated is + Item_subselect with its Item_subselect::eliminated flag which is used + by EXPLAIN code to check if the subquery should be shown in EXPLAIN. - Table elimination is redone on every PS re-execution. + Table elimination is redone on every PS re-execution. (TODO reasons?) */ +/* + A structure that represents a functional dependency of something over + something else. This can be one of: + + 1. A "tbl.field = expr" equality. The field depends on the expression. + + 2. An Item_equal(...) multi-equality. Each participating field depends on + every other participating field. (TODO???) + + 3. A UNIQUE_KEY(field1, field2, fieldN). The key depends on the fields that + it is composed of. + + 4. A table (which is within an outer join nest). Table depends on a unique + key (value of a unique key identifies a table record) + + 5. An outer join nest. It depends on all tables it contains. + +*/ + +class Func_dep : public Sql_alloc +{ +public: + enum { + FD_INVALID, + FD_EXPRESSION, + FD_FIELD, + FD_MULTI_EQUALITY, + FD_UNIQUE_KEY, + FD_TABLE, + FD_OUTER_JOIN + } type; + Func_dep *next; + bool bound; + Func_dep() : next(NULL), bound(FALSE) {} +}; + + +class Field_dep; +class Table_dep; +class Outer_join_dep; + +/* + An equality + - Depends on multiple fields (those in its expression), unknown_args is a + counter of unsatisfied dependencies. +*/ +class Equality_dep : public Func_dep +{ +public: + Field_dep *field; + Item *val; + + uint level; /* Used during condition analysis only */ + uint unknown_args; /* Number of yet unknown arguments */ +}; + + +/* + A field. + - Depends on table or equality + - Has expressions it participates as dependencies + + There is no counter, bound fields are in $list, not bound are not. +*/ +class Field_dep : public Func_dep +{ +public: + Field_dep(Table_dep *table_arg, Field *field_arg) : + table(table_arg), field(field_arg) + { + type= Func_dep::FD_FIELD; + } + /* Table we're from. It also has pointers to keys that we're part of */ + Table_dep *table; + Field *field; + + Field_dep *next_table_field; + uint bitmap_offset; /* Offset of our part of the bitmap */ +}; + + +/* + A unique key. + - Depends on all its components + - Has its table as dependency +*/ +class Key_dep: public Func_dep +{ +public: + Key_dep(Table_dep *table_arg, uint keyno_arg, uint n_parts_arg) : + table(table_arg), keyno(keyno_arg), n_missing_keyparts(n_parts_arg), + next_table_key(NULL) + { + type= Func_dep::FD_UNIQUE_KEY; + } + Table_dep *table; /* Table this key is from */ + uint keyno; // TODO do we care about this + uint n_missing_keyparts; + Key_dep *next_table_key; +}; + + +/* + A table. + - Depends on any of its unique keys + - Has its fields and embedding outer join as dependency. +*/ +class Table_dep : public Func_dep +{ +public: + Table_dep(TABLE *table_arg) : + table(table_arg), fields(NULL), keys(NULL), outer_join_dep(NULL) + { + type= Func_dep::FD_TABLE; + } + TABLE *table; + Field_dep *fields; /* Fields that belong to this table */ + Key_dep *keys; /* Unique keys */ + Outer_join_dep *outer_join_dep; +}; + + +/* + An outer join nest. + - Depends on all tables inside it. + - (And that's it). +*/ +class Outer_join_dep: public Func_dep +{ +public: + Outer_join_dep(TABLE_LIST *table_list_arg, table_map missing_tables_arg) : + table_list(table_list_arg), missing_tables(missing_tables_arg), + all_tables(missing_tables_arg), parent(NULL) + { + type= Func_dep::FD_OUTER_JOIN; + } + TABLE_LIST *table_list; + table_map missing_tables; + table_map all_tables; + Outer_join_dep *parent; +}; + + +/* TODO need this? */ +class Table_elimination +{ +public: + Table_elimination(JOIN *join_arg) : join(join_arg) + { + bzero(table_deps, sizeof(table_deps)); + } + + JOIN *join; + /* Array of equality dependencies */ + Equality_dep *equality_deps; + uint n_equality_deps; /* Number of elements in the array */ + + /* tablenr -> Table_dep* mapping. */ + Table_dep *table_deps[MAX_KEY]; + + /* Outer joins that are candidates for elimination */ + List oj_deps; + + /* Bitmap of how expressions depend on bits */ + MY_BITMAP expr_deps; +}; + + +static +void build_funcdeps_for_cond(Table_elimination *te, Equality_dep **fdeps, + uint *and_level, Item *cond, + table_map usable_tables); +static +void add_funcdep(Table_elimination *te, + Equality_dep **eq_dep, uint and_level, + Item_func *cond, Field *field, + bool eq_func, Item **value, + uint num_values, table_map usable_tables); +static +Equality_dep *merge_func_deps(Equality_dep *start, Equality_dep *new_fields, + Equality_dep *end, uint and_level); + +Field_dep *get_field_dep(Table_elimination *te, Field *field); +void eliminate_tables(JOIN *join); static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl); -static bool table_has_one_match(TABLE *table, table_map bound_tables, - bool *multiple_matches); + +#ifndef DBUG_OFF +static void dbug_print_deps(Table_elimination *te); +#endif + +/*******************************************************************************************/ + +/* + Produce FUNC_DEP elements for the given item (i.e. condition) and add them + to fdeps array. + + SYNOPSIS + build_funcdeps_for_cond() + fdeps INOUT Put created FUNC_DEP structures here + + DESCRIPTION + a + + SEE ALSO + add_key_fields() + +*/ +static +void build_funcdeps_for_cond(Table_elimination *te, + Equality_dep **fdeps, uint *and_level, Item *cond, + table_map usable_tables) +{ + if (cond->type() == Item_func::COND_ITEM) + { + List_iterator_fast li(*((Item_cond*) cond)->argument_list()); + Equality_dep *org_key_fields= *fdeps; + + /* AND/OR */ + if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) + { + Item *item; + while ((item=li++)) + { + build_funcdeps_for_cond(te, fdeps, and_level, item, usable_tables); + } + /* + TODO: inject here a "if we have {t.col=const AND t.col=smth_else}, then + remove the second part" logic. + */ + for (; org_key_fields != *fdeps ; org_key_fields++) + org_key_fields->level= *and_level; + } + else + { + (*and_level)++; + build_funcdeps_for_cond(te, fdeps, and_level, li++, usable_tables); + Item *item; + while ((item=li++)) + { + Equality_dep *start_key_fields= *fdeps; + (*and_level)++; + build_funcdeps_for_cond(te, fdeps, and_level, item, usable_tables); + *fdeps= merge_func_deps(org_key_fields, start_key_fields, *fdeps, + ++(*and_level)); + } + } + return; + } + + if (cond->type() != Item::FUNC_ITEM) + return; + Item_func *cond_func= (Item_func*) cond; + switch (cond_func->select_optimize()) { + case Item_func::OPTIMIZE_NONE: + break; + case Item_func::OPTIMIZE_KEY: + { + Item **values; + // BETWEEN, IN, NE + if (cond_func->key_item()->real_item()->type() == Item::FIELD_ITEM && + !(cond_func->used_tables() & OUTER_REF_TABLE_BIT)) + { + values= cond_func->arguments()+1; + if (cond_func->functype() == Item_func::NE_FUNC && + cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM && + !(cond_func->arguments()[0]->used_tables() & OUTER_REF_TABLE_BIT)) + values--; + DBUG_ASSERT(cond_func->functype() != Item_func::IN_FUNC || + cond_func->argument_count() != 2); + add_funcdep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->key_item()->real_item()))->field, + 0, values, + cond_func->argument_count()-1, + usable_tables); + } + if (cond_func->functype() == Item_func::BETWEEN) + { + values= cond_func->arguments(); + for (uint i= 1 ; i < cond_func->argument_count() ; i++) + { + Item_field *field_item; + if (cond_func->arguments()[i]->real_item()->type() == Item::FIELD_ITEM + && + !(cond_func->arguments()[i]->used_tables() & OUTER_REF_TABLE_BIT)) + { + field_item= (Item_field *) (cond_func->arguments()[i]->real_item()); + add_funcdep(te, fdeps, *and_level, cond_func, + field_item->field, 0, values, 1, usable_tables); + } + } + } + break; + } + case Item_func::OPTIMIZE_OP: + { + bool equal_func=(cond_func->functype() == Item_func::EQ_FUNC || + cond_func->functype() == Item_func::EQUAL_FUNC); + + if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM && + !(cond_func->arguments()[0]->used_tables() & OUTER_REF_TABLE_BIT)) + { + add_funcdep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[0])->real_item())->field, + equal_func, + cond_func->arguments()+1, 1, usable_tables); + } + if (cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM && + cond_func->functype() != Item_func::LIKE_FUNC && + !(cond_func->arguments()[1]->used_tables() & OUTER_REF_TABLE_BIT)) + { + add_funcdep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[1])->real_item())->field, + equal_func, + cond_func->arguments(),1,usable_tables); + } + break; + } + case Item_func::OPTIMIZE_NULL: + /* column_name IS [NOT] NULL */ + if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM && + !(cond_func->used_tables() & OUTER_REF_TABLE_BIT)) + { + Item *tmp=new Item_null; + if (unlikely(!tmp)) // Should never be true + return; + add_funcdep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[0])->real_item())->field, + cond_func->functype() == Item_func::ISNULL_FUNC, + &tmp, 1, usable_tables); + } + break; + case Item_func::OPTIMIZE_EQUAL: + Item_equal *item_equal= (Item_equal *) cond; + Item *const_item= item_equal->get_const(); + Item_equal_iterator it(*item_equal); + Item_field *item; + if (const_item) + { + /* + For each field field1 from item_equal consider the equality + field1=const_item as a condition allowing an index access of the table + with field1 by the keys value of field1. + */ + while ((item= it++)) + { + add_funcdep(te, fdeps, *and_level, cond_func, item->field, + TRUE, &const_item, 1, usable_tables); + } + } + else + { + /* + Consider all pairs of different fields included into item_equal. + For each of them (field1, field1) consider the equality + field1=field2 as a condition allowing an index access of the table + with field1 by the keys value of field2. + */ + Item_equal_iterator fi(*item_equal); + while ((item= fi++)) + { + Field *field= item->field; + while ((item= it++)) + { + if (!field->eq(item->field)) + { + add_funcdep(te, fdeps, *and_level, cond_func, field/*item*/, + TRUE, (Item **) &item, 1, usable_tables); + } + } + it.rewind(); + } + } + break; + } +} + +/* + Perform an OR operation on two (adjacent) FUNC_DEP arrays. + + SYNOPSIS + merge_func_deps() + + DESCRIPTION + + This function is invoked for two adjacent arrays of FUNC_DEP elements: + + $LEFT_PART $RIGHT_PART + +-----------------------+-----------------------+ + start new_fields end + + The goal is to produce an array which would correspnd to the combined + + $LEFT_PART OR $RIGHT_PART + + condition. This is achieved as follows: First, we apply distrubutive law: + + (fdep_A_1 AND fdep_A_2 AND ...) OR (fdep_B_1 AND fdep_B_2 AND ...) = + + = AND_ij (fdep_A_[i] OR fdep_B_[j]) + + Then we walk over the obtained "fdep_A_[i] OR fdep_B_[j]" pairs, and + - Discard those that that have left and right part referring to different + columns. We can't infer anything useful from "col1=expr1 OR col2=expr2". + - When left and right parts refer to the same column, we check if they are + essentially the same. + = If they are the same, we keep one copy + "t.col=expr OR t.col=expr" -> "t.col=expr + = if they are different , then we discard both + "t.col=expr1 OR t.col=expr2" -> (nothing useful) + + (no per-table or for-index FUNC_DEPS exist yet at this phase). + + See also merge_key_fields(). + + RETURN + End of the result array +*/ + +static +Equality_dep *merge_func_deps(Equality_dep *start, Equality_dep *new_fields, + Equality_dep *end, uint and_level) +{ + if (start == new_fields) + return start; // Impossible or + if (new_fields == end) + return start; // No new fields, skip all + + Equality_dep *first_free=new_fields; + + for (; new_fields != end ; new_fields++) + { + for (Equality_dep *old=start ; old != first_free ; old++) + { + /* + TODO: does it make sense to attempt to merging multiple-equalities? + A: YES. + (a=b=c) OR (a=b=d) produce "a=b". + QQ: + What to use for merging? Trivial N*M algorithm or pre-sort and then + merge ordered sequences? + */ + if (old->field == new_fields->field) + { + if (!new_fields->val->const_item()) + { + /* + If the value matches, we can use the key reference. + If not, we keep it until we have examined all new values + */ + if (old->val->eq(new_fields->val, old->field->field->binary())) + { + old->level= and_level; + } + } + else if (old->val->eq_by_collation(new_fields->val, + old->field->field->binary(), + old->field->field->charset())) + { + old->level= and_level; + } + else + { + /* The expressions are different. */ + if (old == --first_free) // If last item + break; + *old= *first_free; // Remove old value + old--; // Retry this value + } + } + } + } + + /* + Ok, the results are within the [start, first_free) range, and the useful + elements have level==and_level. Now, lets remove all unusable elements: + */ + for (Equality_dep *old=start ; old != first_free ;) + { + if (old->level != and_level) + { // Not used in all levels + if (old == --first_free) + break; + *old= *first_free; // Remove old value + continue; + } + old++; + } + return first_free; +} + + +/* + Add a funcdep for a given equality. +*/ + +static +void add_funcdep(Table_elimination *te, + Equality_dep **eq_dep, uint and_level, + Item_func *cond, Field *field, + bool eq_func, Item **value, + uint num_values, table_map usable_tables) +{ + // Field *field= item_field->field; + if (!(field->table->map & usable_tables)) + return; + + for (uint i=0; iused_tables() & RAND_TABLE_BIT) + return; + } + + /* + Save the following cases: + Field op constant + Field LIKE constant where constant doesn't start with a wildcard + Field = field2 where field2 is in a different table + Field op formula + Field IS NULL + Field IS NOT NULL + Field BETWEEN ... + Field IN ... + */ + + /* + We can't always use indexes when comparing a string index to a + number. cmp_type() is checked to allow compare of dates to numbers. + eq_func is NEVER true when num_values > 1 + */ + if (!eq_func) + { + /* + Additional optimization: if we're processing "t.key BETWEEN c1 AND c1" + then proceed as if we were processing "t.key = c1". + */ + if ((cond->functype() != Item_func::BETWEEN) || + ((Item_func_between*) cond)->negated || + !value[0]->eq(value[1], field->binary())) + return; + eq_func= TRUE; + } + + if (field->result_type() == STRING_RESULT) + { + if ((*value)->result_type() != STRING_RESULT) + { + if (field->cmp_type() != (*value)->result_type()) + return; + } + else + { + /* + We can't use indexes if the effective collation + of the operation differ from the field collation. + */ + if (field->cmp_type() == STRING_RESULT && + ((Field_str*)field)->charset() != cond->compare_collation()) + return; + } + } + + DBUG_ASSERT(eq_func); + /* Store possible eq field */ + (*eq_dep)->type= Func_dep::FD_EXPRESSION; //psergey-todo; + (*eq_dep)->field= get_field_dep(te, field); + (*eq_dep)->val= *value; + (*eq_dep)->level= and_level; + (*eq_dep)++; +} + + +Table_dep *get_table_dep(Table_elimination *te, TABLE *table) +{ + Table_dep *tbl_dep= new Table_dep(table); + Key_dep **key_list= &(tbl_dep->keys); + + /* Add dependencies for unique keys */ + for (uint i=0; i < table->s->keys; i++) + { + KEY *key= table->key_info + i; + if ((key->flags & (HA_NOSAME | HA_END_SPACE_KEY)) == HA_NOSAME) + { + Key_dep *key_dep= new Key_dep(tbl_dep, i, key->key_parts); + *key_list= key_dep; + key_list= &(key_dep->next_table_key); + } + } + return te->table_deps[table->tablenr] = tbl_dep; +} + +/* + Given a field, get its dependency element: if it already exists, find it, + otherwise create it. +*/ + +Field_dep *get_field_dep(Table_elimination *te, Field *field) +{ + TABLE *table= field->table; + Table_dep *tbl_dep; + + if (!(tbl_dep= te->table_deps[table->tablenr])) + tbl_dep= get_table_dep(te, table); + + Field_dep **pfield= &(tbl_dep->fields); + while (*pfield && (*pfield)->field->field_index < field->field_index) + { + pfield= &((*pfield)->next_table_field); + } + if (*pfield && (*pfield)->field->field_index == field->field_index) + return *pfield; + + Field_dep *new_field= new Field_dep(tbl_dep, field); + + new_field->next_table_field= *pfield; + *pfield= new_field; + return new_field; +} + + +Outer_join_dep *get_outer_join_dep(Table_elimination *te, + TABLE_LIST *outer_join, table_map deps_map) +{ + Outer_join_dep *oj_dep; + oj_dep= new Outer_join_dep(outer_join, deps_map); + + Table_map_iterator it(deps_map); + int idx; + while ((idx= it.next_bit()) != Table_map_iterator::BITMAP_END) + { + Table_dep *table_dep; + if (!(table_dep= te->table_deps[idx])) + { + TABLE *table= NULL; + /* + Locate and create the table. The search isnt very efficient but + typically we won't get here as we process the ON expression first + and that will create the Table_dep + */ + for (uint i= 0; i < te->join->tables; i++) + { + if (te->join->join_tab[i].table->tablenr == (uint)idx) + { + table= te->join->join_tab[i].table; + break; + } + } + DBUG_ASSERT(table); + table_dep= get_table_dep(te, table); + } + + if (!table_dep->outer_join_dep) + table_dep->outer_join_dep= oj_dep; + else + { + Outer_join_dep *oj= table_dep->outer_join_dep; + while (oj->parent) + oj= oj->parent; + oj->parent=oj_dep; + } + + } + return oj_dep; +} + + +/* + Perform table elimination in a given join list + + SYNOPSIS + collect_funcdeps_for_join_list() + te Table elimination context. + join_list Join list to work on + its_outer_join TRUE <=> the join_list is an inner side of an + outer join + FALSE <=> otherwise (this is top-level join + list, simplify_joins flattens out all + other kinds of join lists) + + tables_in_list Bitmap of tables embedded in the join_list. + tables_used_elsewhere Bitmap of tables that are referred to from + somewhere outside of the join list (e.g. + select list, HAVING, etc). + + DESCRIPTION + Perform table elimination for a join list. + Try eliminating children nests first. + The "all tables in join nest can produce only one matching record + combination" property checking is modeled after constant table detection, + plus we reuse info attempts to eliminate child join nests. + + RETURN + Number of children left after elimination. 0 means everything was + eliminated. +*/ + static uint -eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, - List *join_list, - bool its_outer_join, - table_map tables_in_list, - table_map tables_used_elsewhere, - bool *multiple_matches); -static bool -extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, - KEYUSE *key_start, KEYUSE *key_end, - uint n_keyuses, table_map bound_parts); +collect_funcdeps_for_join_list(Table_elimination *te, + List *join_list, + bool build_eq_deps, + table_map tables_used_elsewhere, + table_map *eliminable_tables, + Equality_dep **eq_dep) +{ + TABLE_LIST *tbl; + List_iterator it(*join_list); + table_map tables_used_on_left= 0; + + while ((tbl= it++)) + { + if (tbl->on_expr) + { + table_map outside_used_tables= tables_used_elsewhere | + tables_used_on_left; + bool eliminable; + table_map cur_map; + if (tbl->nested_join) + { + /* This is "... LEFT JOIN (join_nest) ON cond" */ + cur_map= tbl->nested_join->used_tables; + eliminable= !(cur_map & outside_used_tables); + if (eliminable) + *eliminable_tables |= cur_map; + collect_funcdeps_for_join_list(te, &tbl->nested_join->join_list, + eliminable || build_eq_deps, + outside_used_tables, + eliminable_tables, + eq_dep); + } + else + { + /* This is "... LEFT JOIN tbl ON cond" */ + cur_map= tbl->table->map; + eliminable= !(tbl->table->map & outside_used_tables); + *eliminable_tables |= cur_map; + } + + if (eliminable || build_eq_deps) + { + // build comp_cond from ON expression + uint and_level=0; + build_funcdeps_for_cond(te, eq_dep, &and_level, tbl->on_expr, + *eliminable_tables); + } + + if (eliminable) + te->oj_deps.push_back(get_outer_join_dep(te, tbl, cur_map)); + + tables_used_on_left |= tbl->on_expr->used_tables(); + } + } + return 0; +} + +/* + Analyze exising FUNC_DEP array and add elements for tables and uniq keys + + SYNOPSIS + + DESCRIPTION + Add FUNC_DEP elements + + RETURN + . +*/ + +class Field_dependency_setter : public Field_enumerator +{ +public: + Field_dependency_setter(Table_elimination *te_arg): te(te_arg) + {} + + void see_field(Field *field) + { + Table_dep *tbl_dep; + if ((tbl_dep= te->table_deps[field->table->tablenr])) + { + for (Field_dep *field_dep= tbl_dep->fields; field_dep; + field_dep= field_dep->next_table_field) + { + if (field->field_index == field_dep->field->field_index) + { + uint offs= field_dep->bitmap_offset + expr_offset; + if (!bitmap_is_set(&te->expr_deps, offs)) + te->equality_deps[expr_offset].unknown_args++; + bitmap_set_bit(&te->expr_deps, offs); + return; + } + } + /* We didn't find the field. Bump the dependency anyway */ + te->equality_deps[expr_offset].unknown_args++; + } + } + Table_elimination *te; + uint expr_offset; /* Offset of the expression we're processing */ +}; + + +static +bool setup_equality_deps(Table_elimination *te, Func_dep **bound_deps_list) +{ + DBUG_ENTER("setup_equality_deps"); + + uint offset= 0; + for (Table_dep **tbl_dep=te->table_deps; + tbl_dep < te->table_deps + MAX_TABLES; + tbl_dep++) + { + if (*tbl_dep) + { + for (Field_dep *field_dep= (*tbl_dep)->fields; + field_dep; + field_dep= field_dep->next_table_field) + { + field_dep->bitmap_offset= offset; + offset += te->n_equality_deps; + } + } + } + + void *buf; + if (!(buf= current_thd->alloc(bitmap_buffer_size(offset))) || + bitmap_init(&te->expr_deps, (my_bitmap_map*)buf, offset, FALSE)) + { + DBUG_RETURN(TRUE); + } + bitmap_clear_all(&te->expr_deps); + + /* + Walk through all field=expr elements and collect all fields. + */ + Func_dep *bound_dep= NULL; + Field_dependency_setter deps_setter(te); + for (Equality_dep *eq_dep= te->equality_deps; + eq_dep < te->equality_deps + te->n_equality_deps; + eq_dep++) + { + deps_setter.expr_offset= eq_dep - te->equality_deps; + eq_dep->unknown_args= 0; + eq_dep->val->walk(&Item::check_column_usage_processor, FALSE, + (uchar*)&deps_setter); + if (!eq_dep->unknown_args) + { + eq_dep->next= bound_dep; + bound_dep= eq_dep; + eq_dep->bound= TRUE; + } + } + *bound_deps_list= bound_dep; + + DBUG_EXECUTE("test", dbug_print_deps(te); ); + DBUG_RETURN(FALSE); +} + /* Perform table elimination @@ -99,12 +939,13 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, void eliminate_tables(JOIN *join) { + THD* thd= join->thd; Item *item; table_map used_tables; DBUG_ENTER("eliminate_tables"); DBUG_ASSERT(join->eliminated_tables == 0); - + /* If there are no outer joins, we have nothing to eliminate: */ if (!join->outer_join) DBUG_VOID_RETURN; @@ -126,7 +967,6 @@ void eliminate_tables(JOIN *join) used_tables |= (*(cur_list->item))->used_tables(); } - THD* thd= join->thd; if (join->select_lex == &thd->lex->select_lex) { /* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */ @@ -145,311 +985,161 @@ void eliminate_tables(JOIN *join) if (all_tables & ~used_tables) { /* There are some tables that we probably could eliminate. Try it. */ - TABLE *leaves_array[MAX_TABLES]; - bool multiple_matches= FALSE; - eliminate_tables_for_list(join, leaves_array, join->join_list, FALSE, - all_tables, used_tables, &multiple_matches); - } - DBUG_VOID_RETURN; -} + Table_elimination te(join); + uint m= max(thd->lex->current_select->max_equal_elems,1); + uint max_elems= ((thd->lex->current_select->cond_count+1)*2 + + thd->lex->current_select->between_count)*m + 1 + 10; + if (!(te.equality_deps= new Equality_dep[max_elems])) + DBUG_VOID_RETURN; + Equality_dep *eq_deps_end= te.equality_deps; + table_map eliminable_tables= 0; + collect_funcdeps_for_join_list(&te, join->join_list, + FALSE, + used_tables, + &eliminable_tables, + &eq_deps_end); + te.n_equality_deps= eq_deps_end - te.equality_deps; + Func_dep *bound_dep; + setup_equality_deps(&te, &bound_dep); -/* - Perform table elimination in a given join list + /* + Run the wave. + All Func_dep-derived objects are divided into three classes: + - Those that have bound=FALSE + - Those that have bound=TRUE + - Those that have bound=TRUE and are in the list.. - SYNOPSIS - eliminate_tables_for_list() - join The join - leaves_arr OUT Store here an array of leaf (base) tables that - are descendants of the join_list, and increment - the pointer to point right above the array. - join_list Join list to work on - its_outer_join TRUE <=> join_list is an inner side of an outer - join - FALSE <=> otherwise (this is top-level join list) - tables_in_list Bitmap of tables embedded in the join_list. - tables_used_elsewhere Bitmap of tables that are referred to from - somewhere outside of the join list (e.g. - select list, HAVING, etc). - - DESCRIPTION - Perform table elimination for a join list. - Try eliminating children nests first. - The "all tables in join nest can produce only one matching record - combination" property checking is modeled after constant table detection, - plus we reuse info attempts to eliminate child join nests. - - RETURN - Number of children left after elimination. 0 means everything was - eliminated. -*/ -static uint -eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr, - List *join_list, - bool its_outer_join, - table_map tables_in_list, - table_map tables_used_elsewhere, - bool *multiple_matches) -{ - TABLE_LIST *tbl; - List_iterator it(*join_list); - table_map tables_used_on_left= 0; - TABLE **cur_table= leaves_arr; - bool children_have_multiple_matches= FALSE; - uint remaining_children= 0; - - while ((tbl= it++)) - { - if (tbl->on_expr) - { - table_map outside_used_tables= tables_used_elsewhere | - tables_used_on_left; - bool multiple_matches= FALSE; - if (tbl->nested_join) - { - /* This is "... LEFT JOIN (join_nest) ON cond" */ - uint n; - if (!(n= eliminate_tables_for_list(join, cur_table, - &tbl->nested_join->join_list, TRUE, - tbl->nested_join->used_tables, - outside_used_tables, - &multiple_matches))) - { - mark_as_eliminated(join, tbl); - } - else - remaining_children++; - tbl->nested_join->n_tables= n; - } - else - { - /* This is "... LEFT JOIN tbl ON cond" */ - if (!(tbl->table->map & outside_used_tables) && - table_has_one_match(tbl->table, join->all_tables_map(), - &multiple_matches)) - { - mark_as_eliminated(join, tbl); - } - else - remaining_children++; - } - tables_used_on_left |= tbl->on_expr->used_tables(); - children_have_multiple_matches= children_have_multiple_matches || - multiple_matches; - } - else - { - DBUG_ASSERT(!tbl->nested_join); - remaining_children++; - } - - if (tbl->table) - *(cur_table++)= tbl->table; - } - - *multiple_matches |= children_have_multiple_matches; - - /* Try eliminating the nest we're called for */ - if (its_outer_join && !children_have_multiple_matches && - !(tables_in_list & tables_used_elsewhere)) - { - table_map bound_tables= join->const_table_map | (join->all_tables_map() & - ~tables_in_list); - table_map old_bound_tables; - TABLE **leaves_end= cur_table; - /* - Do the same as const table search table: try to expand the set of bound - tables until it covers all tables in the join_list */ - do + while (bound_dep) { - old_bound_tables= bound_tables; - for (cur_table= leaves_arr; cur_table != leaves_end; cur_table++) + Func_dep *next= bound_dep->next; + //e= list.remove_first(); + switch (bound_dep->type) { - if (!((*cur_table)->map & join->eliminated_tables) && - table_has_one_match(*cur_table, bound_tables, multiple_matches)) + case Func_dep::FD_EXPRESSION: { - bound_tables |= (*cur_table)->map; - } - } - } while (old_bound_tables != bound_tables); - - if (!(tables_in_list & ~bound_tables)) - { - /* - This join_list can be eliminated. Signal about this to the caller by - returning number of tables. - */ - remaining_children= 0; - } - } - return remaining_children; -} - - -/* - Check if the table will produce at most one matching record - - SYNOPSIS - table_has_one_match() - table The [base] table being checked - bound_tables Tables that should be considered bound. - multiple_matches OUT Set to TRUE when there is no way we could - find find a limitation that would give us one-match - property. - - DESCRIPTION - Check if table will produce at most one matching record for each record - combination of tables in bound_tables bitmap. - - The check is based on ref analysis data, KEYUSE structures. We're - handling two cases: - - 1. Table has a UNIQUE KEY(uk_col_1, ... uk_col_N), and for each uk_col_i - there is a KEYUSE that represents a limitation in form - - table.uk_col_i = func(bound_tables) (X) - - 2. Same as above but we also handle limitations in form - - table.uk_col_i = func(bound_tables, uk_col_j1, ... uk_col_j2) (XX) - - where values of uk_col_jN are known to be bound because for them we - have an equality of form (X) or (XX). - - RETURN - TRUE Yes, at most one match - FALSE No -*/ - -static bool table_has_one_match(TABLE *table, table_map bound_tables, - bool *multiple_matches) -{ - KEYUSE *keyuse= table->reginfo.join_tab->keyuse; - if (keyuse) - { - while (keyuse->table == table) - { - uint key= keyuse->key; - key_part_map bound_parts=0; - uint n_unusable=0; - bool ft_key= test(keyuse->keypart == FT_KEYPART); - KEY *keyinfo= table->key_info + key; - KEYUSE *key_start = keyuse; - - do /* For each keypart and each way to read it */ - { - if (keyuse->type == KEYUSE_USABLE) - { - if(!(keyuse->used_tables & ~bound_tables) && - !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) + /* It's a field=expr and we got to know the expr, so we know the field */ + Equality_dep *eq_dep= (Equality_dep*)bound_dep; + if (!eq_dep->field->bound) { - bound_parts |= keyuse->keypart_map; + /* Mark as bound and add to the list */ + eq_dep->field->bound= TRUE; + eq_dep->field->next= next; + next= eq_dep->field; } + break; } - else - n_unusable++; - keyuse++; - } while (keyuse->table == table && keyuse->key == key); - - if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) - != HA_NOSAME)) - { - continue; - } - - if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts) || - extra_keyuses_bind_all_keyparts(bound_tables, table, key_start, - keyuse, n_unusable, bound_parts)) - { - return TRUE; - } - } - } - return FALSE; -} - - -/* - Check if KEYUSE elemements with unusable==TRUE bind all parts of the key - - SYNOPSIS - - extra_keyuses_bind_all_keyparts() - bound_tables Tables which can be considered constants - table Table we're examining - key_start Start of KEYUSE array with elements describing the key - of interest - key_end End of the array + 1 - n_keyuses Number of elements in the array that have unusable==TRUE - bound_parts Key parts whose values are known to be bound. - - DESCRIPTION - Check if unusable KEYUSE elements cause all parts of key to be bound. An - unusable keyuse element makes a keypart bound when it - represents the following: - - keyXpartY=func(bound_columns, preceding_tables) - - RETURN - TRUE Yes, at most one match - FALSE No -*/ - -static bool -extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, - KEYUSE *key_start, KEYUSE *key_end, - uint n_keyuses, table_map bound_parts) -{ - /* - We need - - some 'unusable' KEYUSE elements to work on - - some keyparts to be already bound to start inferences: - */ - if (n_keyuses && bound_parts) - { - KEY *keyinfo= table->key_info + key_start->key; - bool bound_more_parts; - do - { - bound_more_parts= FALSE; - for (KEYUSE *k= key_start; k!=key_end; k++) - { - if (k->type == KEYUSE_UNKNOWN) - { - Field_processor_info fp= {table, k->key, k->keypart, 0, 0}; - if (k->val->walk(&Item::check_column_usage_processor, FALSE, - (uchar*)&fp)) - k->type= KEYUSE_NO_BIND; - else - { - k->used_tables= fp.used_tables; - k->keypart_map= fp.needed_key_parts; - k->type= KEYUSE_BIND; - } - } - - if (k->type == KEYUSE_BIND) + case Func_dep::FD_FIELD: { /* - If this is a binding keyuse, such that - - all tables it refers to are bound, - - all parts it refers to are bound - - but the key part it binds is not itself bound + Field became known. Check out + - unique keys we belong to + - expressions that depend on us. */ - if (!(k->used_tables & ~bound_tables) && - !(k->keypart_map & ~bound_parts) && - !(bound_parts & key_part_map(1) << k->keypart)) + Field_dep *field_dep= (Field_dep*)bound_dep; + for (Key_dep *key_dep= field_dep->table->keys; key_dep; + key_dep= key_dep->next_table_key) { - bound_parts|= key_part_map(1) << k->keypart; - if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) - return TRUE; - bound_more_parts= TRUE; + DBUG_PRINT("info", ("key %s.%s is now bound", + key_dep->table->table->alias, + key_dep->table->table->key_info[key_dep->keyno].name)); + if (!key_dep->bound) + { + if (!--key_dep->n_missing_keyparts) + { + /* Mark as bound and add to the list */ + key_dep->bound= TRUE; + key_dep->next= next; + next= key_dep; + } + } } + + /* Now, expressions */ + for (uint i=0; i < te.n_equality_deps; i++) + { + if (bitmap_is_set(&te.expr_deps, field_dep->bitmap_offset + i)) + { + Equality_dep* eq_dep= &te.equality_deps[i]; + if (!--eq_dep->unknown_args) + { + /* Mark as bound and add to the list */ + eq_dep->bound= TRUE; + eq_dep->next= next; + next= eq_dep; + } + } + } + break; } + case Func_dep::FD_UNIQUE_KEY: + { + /* Unique key is known means the table is known */ + Table_dep *table_dep=((Key_dep*)bound_dep)->table; + if (!table_dep->bound) + { + /* Mark as bound and add to the list */ + table_dep->bound= TRUE; + table_dep->next= next; + next= table_dep; + } + break; + } + case Func_dep::FD_TABLE: + { + Table_dep *table_dep=(Table_dep*)bound_dep; + DBUG_PRINT("info", ("table %s is now bound", + table_dep->table->alias)); + /* + Table is known means + - all its fields are known + - one more element in outer join nest is known + */ + for (Field_dep *field_dep= table_dep->fields; field_dep; + field_dep= field_dep->next_table_field) + { + if (!field_dep->bound) + { + /* Mark as bound and add to the list */ + field_dep->bound= TRUE; + field_dep->next= next; + next= field_dep; + } + } + Outer_join_dep *outer_join_dep= table_dep->outer_join_dep; + if (!(outer_join_dep->missing_tables &= ~table_dep->table->map)) + { + /* Mark as bound and add to the list */ + outer_join_dep->bound= TRUE; + outer_join_dep->next= next; + next= outer_join_dep; + } + break; + } + case Func_dep::FD_OUTER_JOIN: + { + Outer_join_dep *outer_join_dep= (Outer_join_dep*)bound_dep; + /* TODO what do here? Stop if eliminated the top-level? */ + mark_as_eliminated(te.join, outer_join_dep->table_list); + Outer_join_dep *parent= outer_join_dep->parent; + if (parent && + !(parent->missing_tables &= ~outer_join_dep->all_tables)) + { + /* Mark as bound and add to the list */ + parent->bound= TRUE; + parent->next= next; + next= parent; + } + break; + } + case Func_dep::FD_MULTI_EQUALITY: + default: + DBUG_ASSERT(0); } - } while (bound_more_parts); + bound_dep= next; + } } - return FALSE; + DBUG_VOID_RETURN; } @@ -488,6 +1178,96 @@ static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl) tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL); } + + +#ifndef DBUG_OFF +static +void dbug_print_deps(Table_elimination *te) +{ + DBUG_ENTER("dbug_print_deps"); + DBUG_LOCK_FILE; + + fprintf(DBUG_FILE,"deps {\n"); + + /* Start with printing equalities */ + for (Equality_dep *eq_dep= te->equality_deps; + eq_dep != te->equality_deps + te->n_equality_deps; eq_dep++) + { + char buf[128]; + String str(buf, sizeof(buf), &my_charset_bin); + str.length(0); + eq_dep->val->print(&str, QT_ORDINARY); + fprintf(DBUG_FILE, " equality%d: %s -> %s.%s\n", + eq_dep - te->equality_deps, + str.c_ptr(), + eq_dep->field->table->table->alias, + eq_dep->field->field->field_name); + } + fprintf(DBUG_FILE,"\n"); + + /* Then tables and their fields */ + for (uint i=0; i < MAX_TABLES; i++) + { + Table_dep *table_dep; + if ((table_dep= te->table_deps[i])) + { + /* Print table */ + fprintf(DBUG_FILE, " table %s\n", table_dep->table->alias); + /* Print fields */ + for (Field_dep *field_dep= table_dep->fields; field_dep; + field_dep= field_dep->next_table_field) + { + fprintf(DBUG_FILE, " field %s.%s ->", table_dep->table->alias, + field_dep->field->field_name); + uint ofs= field_dep->bitmap_offset; + for (uint bit= ofs; bit < ofs + te->n_equality_deps; bit++) + { + if (bitmap_is_set(&te->expr_deps, bit)) + fprintf(DBUG_FILE, " equality%d ", bit - ofs); + } + fprintf(DBUG_FILE, "\n"); + } + } + } + fprintf(DBUG_FILE,"\n}\n"); + DBUG_UNLOCK_FILE; + DBUG_VOID_RETURN; +} + +#endif + +/***********************************************************************************************/ + +#if 0 +static void dbug_print_fdep(FUNC_DEP *fd) +{ + switch (fd->type) { + case FUNC_DEP::FD_OUTER_JOIN: + { + fprintf(DBUG_FILE, "outer_join("); + if (fd->table_list->nested_join) + { + bool first= TRUE; + List_iterator it(fd->table_list->nested_join->join_list); + TABLE_LIST *tbl; + while ((tbl= it++)) + { + fprintf(DBUG_FILE, "%s%s", first?"":" ", + tbl->table? tbl->table->alias : "..."); + first= FALSE; + } + fprintf(DBUG_FILE, ")"); + } + else + fprintf(DBUG_FILE, "%s", fd->table_list->table->alias); + fprintf(DBUG_FILE, ")"); + break; + } + } +} + +#endif + /** @} (end of group Table_Elimination) */ diff --git a/sql/sql_bitmap.h b/sql/sql_bitmap.h index 97accefe8aa..e07806a56ab 100644 --- a/sql/sql_bitmap.h +++ b/sql/sql_bitmap.h @@ -93,6 +93,34 @@ public: } }; +/* An iterator to quickly walk over bits in unlonglong bitmap. */ +class Table_map_iterator +{ + ulonglong bmp; + uint no; +public: + Table_map_iterator(ulonglong t) : bmp(t), no(0) {} + int next_bit() + { + static const char last_bit[16]= {32, 0, 1, 0, + 2, 0, 1, 0, + 3, 0, 1, 0, + 2, 0, 1, 0}; + uint bit; + while ((bit= last_bit[bmp & 0xF]) == 32) + { + no += 4; + bmp= bmp >> 4; + if (!bmp) + return BITMAP_END; + } + bmp &= ~(1LL << bit); + return no + bit; + } + int operator++(int) { return next_bit(); } + enum { BITMAP_END= 64 }; +}; + template <> class Bitmap<64> { ulonglong map; @@ -136,5 +164,10 @@ public: my_bool operator==(const Bitmap<64>& map2) const { return map == map2.map; } char *print(char *buf) const { longlong2str(map,buf,16); return buf; } ulonglong to_ulonglong() const { return map; } + class Iterator : public Table_map_iterator + { + public: + Iterator(Bitmap<64> &bmp) : Table_map_iterator(bmp.map) {} + }; }; From b032c7d833d8f4de7eef714fc43ade84d53537a7 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 03:43:02 +0400 Subject: [PATCH 22/27] MWL#17: Table elimination - Continue addressing review feedback: remove "unusable KEYUSEs" extension as it is no longer needed. sql/item.h: MWL#17: Table elimination - Code cleanup sql/opt_table_elimination.cc: MWL#17: Table elimination - Code cleanup --- sql/item.h | 12 ------- sql/opt_table_elimination.cc | 33 ------------------ sql/sql_select.cc | 65 +++++++++++------------------------- sql/sql_select.h | 50 +-------------------------- 4 files changed, 21 insertions(+), 139 deletions(-) diff --git a/sql/item.h b/sql/item.h index 53ae8ab8826..205c72ede9c 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1017,18 +1017,6 @@ public: bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs); }; -#if 0 -typedef struct -{ - TABLE *table; /* Table of interest */ - uint keyno; /* Index of interest */ - uint forbidden_part; /* key part which one is not allowed to refer to */ - /* [Set by processor] used tables, besides the table of interest */ - table_map used_tables; - /* [Set by processor] Parts of index of interest that expression refers to */ - uint needed_key_parts; -} Field_processor_info; -#endif /* Data for Item::check_column_usage_processor */ class Field_enumerator diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index e5829b1eb96..25f8d926ac6 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -1119,7 +1119,6 @@ void eliminate_tables(JOIN *join) case Func_dep::FD_OUTER_JOIN: { Outer_join_dep *outer_join_dep= (Outer_join_dep*)bound_dep; - /* TODO what do here? Stop if eliminated the top-level? */ mark_as_eliminated(te.join, outer_join_dep->table_list); Outer_join_dep *parent= outer_join_dep->parent; if (parent && @@ -1236,38 +1235,6 @@ void dbug_print_deps(Table_elimination *te) #endif -/***********************************************************************************************/ - -#if 0 -static void dbug_print_fdep(FUNC_DEP *fd) -{ - switch (fd->type) { - case FUNC_DEP::FD_OUTER_JOIN: - { - fprintf(DBUG_FILE, "outer_join("); - if (fd->table_list->nested_join) - { - bool first= TRUE; - List_iterator it(fd->table_list->nested_join->join_list); - TABLE_LIST *tbl; - while ((tbl= it++)) - { - fprintf(DBUG_FILE, "%s%s", first?"":" ", - tbl->table? tbl->table->alias : "..."); - first= FALSE; - } - fprintf(DBUG_FILE, ")"); - } - else - fprintf(DBUG_FILE, "%s", fd->table_list->table->alias); - fprintf(DBUG_FILE, ")"); - break; - } - } -} - -#endif - /** @} (end of group Table_Elimination) */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b5771bc0d42..338010aee1b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2474,7 +2474,6 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ } - /* This structure is used to collect info on potentially sargable predicates in order to check whether they become sargable after @@ -2762,16 +2761,14 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, { start_keyuse=keyuse; key=keyuse->key; - if (keyuse->type == KEYUSE_USABLE) - s->keys.set_bit(key); // QQ: remove this ? + s->keys.set_bit(key); // QQ: remove this ? refs=0; const_ref.clear_all(); eq_part.clear_all(); do { - if (keyuse->type == KEYUSE_USABLE && - keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) + if (keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) { if (!((~found_const_table_map) & keyuse->used_tables)) const_ref.set_bit(keyuse->keypart); @@ -2971,9 +2968,11 @@ typedef struct key_field_t { */ bool null_rejecting; bool *cond_guard; /* See KEYUSE::cond_guard */ - enum keyuse_type type; /* See KEYUSE::type */ } KEY_FIELD; +/* Values in optimize */ +#define KEY_OPTIMIZE_EXISTS 1 +#define KEY_OPTIMIZE_REF_OR_NULL 2 /** Merge new key definitions to old ones, remove those not used in both. @@ -3064,18 +3063,13 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_REF_OR_NULL)); old->null_rejecting= (old->null_rejecting && new_fields->null_rejecting); - /* - The conditions are the same, hence their usabilities should - be, too (TODO: shouldn't that apply to the above - null_rejecting and optimize attributes?) - */ - DBUG_ASSERT(old->type == new_fields->type); } } else if (old->eq_func && new_fields->eq_func && old->val->eq_by_collation(new_fields->val, old->field->binary(), old->field->charset())) + { old->level= and_level; old->optimize= ((old->optimize & new_fields->optimize & @@ -3084,15 +3078,10 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_REF_OR_NULL)); old->null_rejecting= (old->null_rejecting && new_fields->null_rejecting); - // "t.key_col=const" predicates are always usable - DBUG_ASSERT(old->type == KEYUSE_USABLE && - new_fields->type == KEYUSE_USABLE); } else if (old->eq_func && new_fields->eq_func && - ((new_fields->type == KEYUSE_USABLE && - old->val->const_item() && old->val->is_null()) || - ((old->type == KEYUSE_USABLE && new_fields->val->is_null())))) - /* TODO ^ why is the above asymmetric, why const_item()? */ + ((old->val->const_item() && old->val->is_null()) || + new_fields->val->is_null())) { /* field = expression OR field IS NULL */ old->level= and_level; @@ -3163,7 +3152,6 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, table_map usable_tables, SARGABLE_PARAM **sargables) { uint exists_optimize= 0; - bool optimizable=0; if (!(field->flags & PART_KEY_FLAG)) { // Don't remove column IS NULL on a LEFT JOIN table @@ -3176,12 +3164,15 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, else { table_map used_tables=0; + bool optimizable=0; for (uint i=0; iused_tables(); if (!((value[i])->used_tables() & (field->table->map | RAND_TABLE_BIT))) optimizable=1; } + if (!optimizable) + return; if (!(usable_tables & field->table->map)) { if (!eq_func || (*value)->type() != Item::NULL_ITEM || @@ -3194,8 +3185,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, JOIN_TAB *stat=field->table->reginfo.join_tab; key_map possible_keys=field->key_start; possible_keys.intersect(field->table->keys_in_use_for_query); - if (optimizable) - stat[0].keys.merge(possible_keys); // Add possible keys + stat[0].keys.merge(possible_keys); // Add possible keys /* Save the following cases: @@ -3288,7 +3278,6 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, (*key_fields)->val= *value; (*key_fields)->level= and_level; (*key_fields)->optimize= exists_optimize; - (*key_fields)->type= optimizable? KEYUSE_USABLE : KEYUSE_UNKNOWN; /* If the condition has form "tbl.keypart = othertbl.field" and othertbl.field can be NULL, there will be no matches if othertbl.field @@ -3600,7 +3589,6 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.null_rejecting= key_field->null_rejecting; keyuse.cond_guard= key_field->cond_guard; - keyuse.type= key_field->type; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } } @@ -3609,6 +3597,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) } +#define FT_KEYPART (MAX_REF_PARTS+10) static void add_ft_keys(DYNAMIC_ARRAY *keyuse_array, @@ -3667,7 +3656,6 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, keyuse.used_tables=cond_func->key_item()->used_tables(); keyuse.optimize= 0; keyuse.keypart_map= 0; - keyuse.type= KEYUSE_USABLE; VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); } @@ -3682,13 +3670,6 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) return (int) (a->key - b->key); if (a->keypart != b->keypart) return (int) (a->keypart - b->keypart); - - // Usable ones go before the unusable - int a_ok= test(a->type == KEYUSE_USABLE); - int b_ok= test(b->type == KEYUSE_USABLE); - if (a_ok != b_ok) - return a_ok? -1 : 1; - // Place const values before other ones if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - test((b->used_tables & ~OUTER_REF_TABLE_BIT)))) @@ -3899,8 +3880,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, found_eq_constant=0; for (i=0 ; i < keyuse->elements-1 ; i++,use++) { - if (use->type == KEYUSE_USABLE && !use->used_tables && - use->optimize != KEY_OPTIMIZE_REF_OR_NULL) + if (!use->used_tables && use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->table->const_key_parts[use->key]|= use->keypart_map; if (use->keypart != FT_KEYPART) { @@ -3924,8 +3904,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, /* Save ptr to first use */ if (!use->table->reginfo.join_tab->keyuse) use->table->reginfo.join_tab->keyuse=save_pos; - if (use->type == KEYUSE_USABLE) - use->table->reginfo.join_tab->checked_keys.set_bit(use->key); + use->table->reginfo.join_tab->checked_keys.set_bit(use->key); save_pos++; } i=(uint) (save_pos-(KEYUSE*) keyuse->buffer); @@ -3955,7 +3934,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) To avoid bad matches, we don't make ref_table_rows less than 100. */ keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref - if (keyuse->type == KEYUSE_USABLE && keyuse->used_tables & + if (keyuse->used_tables & (map= (keyuse->used_tables & ~join->const_table_map & ~OUTER_REF_TABLE_BIT))) { @@ -4147,8 +4126,7 @@ best_access_path(JOIN *join, if 1. expression doesn't refer to forward tables 2. we won't get two ref-or-null's */ - if (keyuse->type == KEYUSE_USABLE && - !(remaining_tables & keyuse->used_tables) && + if (!(remaining_tables & keyuse->used_tables) && !(ref_or_null_part && (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))) { @@ -5602,8 +5580,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, */ do { - if (!(~used_tables & keyuse->used_tables) && - keyuse->type == KEYUSE_USABLE) + if (!(~used_tables & keyuse->used_tables)) { if (keyparts == keyuse->keypart && !(found_part_ref_or_null & keyuse->optimize)) @@ -5653,11 +5630,9 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, uint i; for (i=0 ; i < keyparts ; keyuse++,i++) { - while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) || - !(keyuse->type == KEYUSE_USABLE)) - { + while (keyuse->keypart != i || + ((~used_tables) & keyuse->used_tables)) keyuse++; /* Skip other parts */ - } uint maybe_null= test(keyinfo->key_part[i].null_bit); j->ref.items[i]=keyuse->val; // Save for cond removal diff --git a/sql/sql_select.h b/sql/sql_select.h index c3d0dacd78b..271c88ebf66 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -28,45 +28,6 @@ #include "procedure.h" #include -#define FT_KEYPART (MAX_REF_PARTS+10) -/* Values in optimize */ -#define KEY_OPTIMIZE_EXISTS 1 -#define KEY_OPTIMIZE_REF_OR_NULL 2 - -/* KEYUSE element types */ -enum keyuse_type -{ - /* - val refers to the same table, this is either KEYUSE_BIND or KEYUSE_NO_BIND - type, we didn't determine which one yet. - */ - KEYUSE_UNKNOWN= 0, - /* - 'regular' keyuse, i.e. it represents one of the following - * t.keyXpartY = func(constants, other-tables) - * t.keyXpartY IS NULL - * t.keyXpartY = func(constants, other-tables) OR t.keyXpartY IS NULL - and can be used to construct ref acces - */ - KEYUSE_USABLE, - /* - The keyuse represents a condition in form: - - t.uniq_keyXpartY = func(other parts of uniq_keyX) - - This can't be used to construct uniq_keyX but we could use it to determine - that the table will produce at most one match. - */ - KEYUSE_BIND, - /* - Keyuse that's not usable for ref access and doesn't meet the criteria of - KEYUSE_BIND. Examples: - t.keyXpartY = func(t.keyXpartY) - t.keyXpartY = func(column of t that's not covered by keyX) - */ - KEYUSE_NO_BIND -}; - typedef struct keyuse_t { TABLE *table; Item *val; /**< or value if no field */ @@ -90,15 +51,6 @@ typedef struct keyuse_t { NULL - Otherwise (the source equality can't be turned off) */ bool *cond_guard; - /* - 1 <=> This keyuse can be used to construct key access. - 0 <=> Otherwise. Currently unusable KEYUSEs represent equalities - where one table column refers to another one, like this: - t.keyXpartA=func(t.keyXpartB) - This equality cannot be used for index access but is useful - for table elimination. - */ - enum keyuse_type type; } KEYUSE; class store_key; @@ -258,7 +210,7 @@ typedef struct st_join_table { JOIN *join; /** Bitmap of nested joins this table is part of */ nested_join_map embedding_map; - + void cleanup(); inline bool is_using_loose_index_scan() { From 40bb97b525c03faf5bd3c96a1a7c539de44bb337 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 04:01:43 +0400 Subject: [PATCH 23/27] MWL#17: Table elimination - When making inferences "field is bound" -> "key is bound", do check that the field is part of the key --- sql/opt_table_elimination.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 25f8d926ac6..deb4e8c860d 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -1043,7 +1043,8 @@ void eliminate_tables(JOIN *join) DBUG_PRINT("info", ("key %s.%s is now bound", key_dep->table->table->alias, key_dep->table->table->key_info[key_dep->keyno].name)); - if (!key_dep->bound) + if (field_dep->field->part_of_key.is_set(key_dep->keyno) && + !key_dep->bound) { if (!--key_dep->n_missing_keyparts) { From b3f18c88664bfb774c84d5a427161f6bfffda1a8 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 13:24:02 +0400 Subject: [PATCH 24/27] MWL#17: Table elimination - Post-postreview changes fix: Do set NESTED_JOIN::n_tables to number of tables left after elimination. --- sql/sql_select.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 338010aee1b..72136e751bf 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -114,7 +114,7 @@ static COND *simplify_joins(JOIN *join, List *join_list, COND *conds, bool top); static bool check_interleaving_with_nj(JOIN_TAB *next); static void restore_prev_nj_state(JOIN_TAB *last); -static void reset_nj_counters(List *join_list); +static void reset_nj_counters(JOIN *join, List *join_list); static uint build_bitmap_for_nested_joins(List *join_list, uint first_unused); @@ -1011,7 +1011,7 @@ JOIN::optimize() DBUG_RETURN(1); } - reset_nj_counters(join_list); + reset_nj_counters(this, join_list); make_outerjoin_info(this); /* @@ -4625,7 +4625,7 @@ choose_plan(JOIN *join, table_map join_tables) DBUG_ENTER("choose_plan"); join->cur_embedding_map= 0; - reset_nj_counters(join->join_list); + reset_nj_counters(join, join->join_list); /* if (SELECT_STRAIGHT_JOIN option is set) reorder tables so dependent tables come after tables they depend @@ -8791,7 +8791,7 @@ static uint build_bitmap_for_nested_joins(List *join_list, tables which will be ignored. */ -static void reset_nj_counters(List *join_list) +static void reset_nj_counters(JOIN *join, List *join_list) { List_iterator li(*join_list); TABLE_LIST *table; @@ -8802,7 +8802,9 @@ static void reset_nj_counters(List *join_list) if ((nested_join= table->nested_join)) { nested_join->counter= 0; - reset_nj_counters(&nested_join->join_list); + nested_join->n_tables= my_count_bits(nested_join->used_tables & + ~join->eliminated_tables); + reset_nj_counters(join, &nested_join->join_list); } } DBUG_VOID_RETURN; From 536754c8c65e223207f2f631c7eda4ee4a6fc7d1 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 13:36:13 +0400 Subject: [PATCH 25/27] MWL#17: Table elimination Fixes after post-review fixes: - Don't search for tables in JOIN_TAB array. it's not initialized yet. use select_lex->leaf_tables instead. --- sql/opt_table_elimination.cc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index deb4e8c860d..5f98288be96 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -676,16 +676,12 @@ Outer_join_dep *get_outer_join_dep(Table_elimination *te, if (!(table_dep= te->table_deps[idx])) { TABLE *table= NULL; - /* - Locate and create the table. The search isnt very efficient but - typically we won't get here as we process the ON expression first - and that will create the Table_dep - */ - for (uint i= 0; i < te->join->tables; i++) + for (TABLE_LIST *tlist= te->join->select_lex->leaf_tables; tlist; + tlist=tlist->next_leaf) { - if (te->join->join_tab[i].table->tablenr == (uint)idx) + if (tlist->table->tablenr == (uint)idx) { - table= te->join->join_tab[i].table; + table=tlist->table; break; } } From a28390364b0abd493e5e7c968bfd1edee1252248 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 13 Aug 2009 23:10:53 +0400 Subject: [PATCH 26/27] MWL#17: Table elimination - Better comments sql/sql_select.cc: MWL#17: Table elimination - Fix buildbot failure: do set correct value to nested_join::n_tables --- sql/opt_table_elimination.cc | 54 +++++++++++++++--------------------- sql/sql_select.cc | 15 ++++++---- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 5f98288be96..ba0adbef696 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -20,18 +20,15 @@ OVERVIEW The module has one entry point - eliminate_tables() function, which one - needs to call (once) sometime after update_ref_and_keys() but before the - join optimization. + needs to call (once) at some point before the join optimization. eliminate_tables() operates over the JOIN structures. Logically, it removes the right sides of outer join nests. Physically, it changes the following members: * Eliminated tables are marked as constant and moved to the front of the join order. - * In addition to this, they are recorded in JOIN::eliminated_tables bitmap. - * All join nests have their NESTED_JOIN::n_tables updated to discount - the eliminated tables + * In addition to this, they are recorded in JOIN::eliminated_tables bitmap. * Items that became disused because they were in the ON expression of an eliminated outer join are notified by means of the Item tree walk which @@ -40,26 +37,13 @@ Item_subselect with its Item_subselect::eliminated flag which is used by EXPLAIN code to check if the subquery should be shown in EXPLAIN. - Table elimination is redone on every PS re-execution. (TODO reasons?) + Table elimination is redone on every PS re-execution. */ + /* - A structure that represents a functional dependency of something over - something else. This can be one of: - - 1. A "tbl.field = expr" equality. The field depends on the expression. - - 2. An Item_equal(...) multi-equality. Each participating field depends on - every other participating field. (TODO???) - - 3. A UNIQUE_KEY(field1, field2, fieldN). The key depends on the fields that - it is composed of. - - 4. A table (which is within an outer join nest). Table depends on a unique - key (value of a unique key identifies a table record) - - 5. An outer join nest. It depends on all tables it contains. - + An abstract structure that represents some entity that's being dependent on + some other entity. */ class Func_dep : public Sql_alloc @@ -73,9 +57,14 @@ public: FD_UNIQUE_KEY, FD_TABLE, FD_OUTER_JOIN - } type; - Func_dep *next; - bool bound; + } type; /* Type of the object */ + + /* + Used to make a linked list of elements that became bound and thus can + make elements that depend on them bound, too. + */ + Func_dep *next; + bool bound; /* TRUE<=> The entity is considered bound */ Func_dep() : next(NULL), bound(FALSE) {} }; @@ -84,10 +73,10 @@ class Field_dep; class Table_dep; class Outer_join_dep; + /* - An equality - - Depends on multiple fields (those in its expression), unknown_args is a - counter of unsatisfied dependencies. + A "tbl.column= expr" equality dependency. tbl.column depends on fields + used in expr. */ class Equality_dep : public Func_dep { @@ -95,8 +84,11 @@ public: Field_dep *field; Item *val; - uint level; /* Used during condition analysis only */ - uint unknown_args; /* Number of yet unknown arguments */ + /* Used during condition analysis only, similar to KEYUSE::level */ + uint level; + + /* Number of fields referenced from *val that are not yet 'bound' */ + uint unknown_args; }; @@ -139,7 +131,7 @@ public: type= Func_dep::FD_UNIQUE_KEY; } Table_dep *table; /* Table this key is from */ - uint keyno; // TODO do we care about this + uint keyno; uint n_missing_keyparts; Key_dep *next_table_key; }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 72136e751bf..051e259879d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -114,7 +114,7 @@ static COND *simplify_joins(JOIN *join, List *join_list, COND *conds, bool top); static bool check_interleaving_with_nj(JOIN_TAB *next); static void restore_prev_nj_state(JOIN_TAB *last); -static void reset_nj_counters(JOIN *join, List *join_list); +static uint reset_nj_counters(JOIN *join, List *join_list); static uint build_bitmap_for_nested_joins(List *join_list, uint first_unused); @@ -8791,23 +8791,26 @@ static uint build_bitmap_for_nested_joins(List *join_list, tables which will be ignored. */ -static void reset_nj_counters(JOIN *join, List *join_list) +static uint reset_nj_counters(JOIN *join, List *join_list) { List_iterator li(*join_list); TABLE_LIST *table; DBUG_ENTER("reset_nj_counters"); + uint n=0; while ((table= li++)) { NESTED_JOIN *nested_join; if ((nested_join= table->nested_join)) { nested_join->counter= 0; - nested_join->n_tables= my_count_bits(nested_join->used_tables & - ~join->eliminated_tables); - reset_nj_counters(join, &nested_join->join_list); + //nested_join->n_tables= my_count_bits(nested_join->used_tables & + // ~join->eliminated_tables); + nested_join->n_tables= reset_nj_counters(join, &nested_join->join_list); } + if (table->table && (table->table->map & ~join->eliminated_tables)) + n++; } - DBUG_VOID_RETURN; + DBUG_RETURN(n); } From fef409cb9eb6eb4f82bd1a8beba28545f71faf2e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 14 Aug 2009 00:44:52 +0400 Subject: [PATCH 27/27] MWL#17: Table elimination - More function renames, added comments --- sql/opt_table_elimination.cc | 313 +++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 124 deletions(-) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index ba0adbef696..b0e164b05cc 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -93,11 +93,9 @@ public: /* - A field. - - Depends on table or equality - - Has expressions it participates as dependencies - - There is no counter, bound fields are in $list, not bound are not. + A table field. There is only one such object for any tblX.fieldY + - the field epends on its table and equalities + - expressions that use the field are its dependencies */ class Field_dep : public Func_dep { @@ -107,19 +105,23 @@ public: { type= Func_dep::FD_FIELD; } - /* Table we're from. It also has pointers to keys that we're part of */ - Table_dep *table; + + Table_dep *table; /* Table this field is from */ Field *field; + /* + Field_deps that belong to one table form a linked list. list members are + ordered by field_index + */ Field_dep *next_table_field; uint bitmap_offset; /* Offset of our part of the bitmap */ }; /* - A unique key. - - Depends on all its components - - Has its table as dependency + A Unique key. + - Unique key depends on all of its components + - Key's table is its dependency */ class Key_dep: public Func_dep { @@ -133,14 +135,15 @@ public: Table_dep *table; /* Table this key is from */ uint keyno; uint n_missing_keyparts; + /* Unique keys form a linked list, ordered by keyno */ Key_dep *next_table_key; }; /* - A table. - - Depends on any of its unique keys - - Has its fields and embedding outer join as dependency. + A table. + - table depends on any of its unique keys + - has its fields and embedding outer join as dependency. */ class Table_dep : public Func_dep { @@ -151,16 +154,16 @@ public: type= Func_dep::FD_TABLE; } TABLE *table; - Field_dep *fields; /* Fields that belong to this table */ - Key_dep *keys; /* Unique keys */ - Outer_join_dep *outer_join_dep; + Field_dep *fields; /* Ordered list of fields that belong to this table */ + Key_dep *keys; /* Ordered list of Unique keys in this table */ + Outer_join_dep *outer_join_dep; /* Innermost eliminable outer join we're in */ }; /* - An outer join nest. - - Depends on all tables inside it. - - (And that's it). + An outer join nest that is subject to elimination + - it depends on all tables inside it + - has its parent outer join as dependency */ class Outer_join_dep: public Func_dep { @@ -171,14 +174,27 @@ public: { type= Func_dep::FD_OUTER_JOIN; } + /* + Outer join we're representing. This can be a join nest or a one table that + is outer join'ed. + */ TABLE_LIST *table_list; + + /* + Tables within this outer join (and its descendants) that are not yet known + to be functionally dependent. + */ table_map missing_tables; + /* All tables within this outer join and its descendants */ table_map all_tables; + /* Parent eliminable outer join, if any */ Outer_join_dep *parent; }; -/* TODO need this? */ +/* + Table elimination context +*/ class Table_elimination { public: @@ -204,20 +220,22 @@ public: static -void build_funcdeps_for_cond(Table_elimination *te, Equality_dep **fdeps, - uint *and_level, Item *cond, - table_map usable_tables); +void build_eq_deps_for_cond(Table_elimination *te, Equality_dep **fdeps, + uint *and_level, Item *cond, + table_map usable_tables); static -void add_funcdep(Table_elimination *te, - Equality_dep **eq_dep, uint and_level, - Item_func *cond, Field *field, - bool eq_func, Item **value, - uint num_values, table_map usable_tables); +void add_eq_dep(Table_elimination *te, + Equality_dep **eq_dep, uint and_level, + Item_func *cond, Field *field, + bool eq_func, Item **value, + uint num_values, table_map usable_tables); static Equality_dep *merge_func_deps(Equality_dep *start, Equality_dep *new_fields, Equality_dep *end, uint and_level); -Field_dep *get_field_dep(Table_elimination *te, Field *field); +static Table_dep *get_table_dep(Table_elimination *te, TABLE *table); +static Field_dep *get_field_dep(Table_elimination *te, Field *field); + void eliminate_tables(JOIN *join); static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl); @@ -228,24 +246,25 @@ static void dbug_print_deps(Table_elimination *te); /*******************************************************************************************/ /* - Produce FUNC_DEP elements for the given item (i.e. condition) and add them - to fdeps array. + Produce Eq_dep elements for given condition. SYNOPSIS - build_funcdeps_for_cond() - fdeps INOUT Put created FUNC_DEP structures here - + build_eq_deps_for_cond() + te Table elimination context + fdeps INOUT Put produced equality conditions here + and_level INOUT AND-level (like in add_key_fields) + cond Condition to process + usable_tables Tables which fields we're interested in. That is, + Equality_dep represent "tbl.col=expr" and we'll + produce them only if tbl is in usable_tables. DESCRIPTION - a - - SEE ALSO - add_key_fields() - + This function is modeled after add_key_fields() */ + static -void build_funcdeps_for_cond(Table_elimination *te, - Equality_dep **fdeps, uint *and_level, Item *cond, - table_map usable_tables) +void build_eq_deps_for_cond(Table_elimination *te, Equality_dep **fdeps, + uint *and_level, Item *cond, + table_map usable_tables) { if (cond->type() == Item_func::COND_ITEM) { @@ -258,7 +277,7 @@ void build_funcdeps_for_cond(Table_elimination *te, Item *item; while ((item=li++)) { - build_funcdeps_for_cond(te, fdeps, and_level, item, usable_tables); + build_eq_deps_for_cond(te, fdeps, and_level, item, usable_tables); } /* TODO: inject here a "if we have {t.col=const AND t.col=smth_else}, then @@ -270,13 +289,13 @@ void build_funcdeps_for_cond(Table_elimination *te, else { (*and_level)++; - build_funcdeps_for_cond(te, fdeps, and_level, li++, usable_tables); + build_eq_deps_for_cond(te, fdeps, and_level, li++, usable_tables); Item *item; while ((item=li++)) { Equality_dep *start_key_fields= *fdeps; (*and_level)++; - build_funcdeps_for_cond(te, fdeps, and_level, item, usable_tables); + build_eq_deps_for_cond(te, fdeps, and_level, item, usable_tables); *fdeps= merge_func_deps(org_key_fields, start_key_fields, *fdeps, ++(*and_level)); } @@ -304,11 +323,11 @@ void build_funcdeps_for_cond(Table_elimination *te, values--; DBUG_ASSERT(cond_func->functype() != Item_func::IN_FUNC || cond_func->argument_count() != 2); - add_funcdep(te, fdeps, *and_level, cond_func, - ((Item_field*)(cond_func->key_item()->real_item()))->field, - 0, values, - cond_func->argument_count()-1, - usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->key_item()->real_item()))->field, + 0, values, + cond_func->argument_count()-1, + usable_tables); } if (cond_func->functype() == Item_func::BETWEEN) { @@ -321,8 +340,8 @@ void build_funcdeps_for_cond(Table_elimination *te, !(cond_func->arguments()[i]->used_tables() & OUTER_REF_TABLE_BIT)) { field_item= (Item_field *) (cond_func->arguments()[i]->real_item()); - add_funcdep(te, fdeps, *and_level, cond_func, - field_item->field, 0, values, 1, usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, + field_item->field, 0, values, 1, usable_tables); } } } @@ -336,19 +355,19 @@ void build_funcdeps_for_cond(Table_elimination *te, if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM && !(cond_func->arguments()[0]->used_tables() & OUTER_REF_TABLE_BIT)) { - add_funcdep(te, fdeps, *and_level, cond_func, - ((Item_field*)(cond_func->arguments()[0])->real_item())->field, - equal_func, - cond_func->arguments()+1, 1, usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[0])->real_item())->field, + equal_func, + cond_func->arguments()+1, 1, usable_tables); } if (cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM && cond_func->functype() != Item_func::LIKE_FUNC && !(cond_func->arguments()[1]->used_tables() & OUTER_REF_TABLE_BIT)) { - add_funcdep(te, fdeps, *and_level, cond_func, - ((Item_field*)(cond_func->arguments()[1])->real_item())->field, - equal_func, - cond_func->arguments(),1,usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[1])->real_item())->field, + equal_func, + cond_func->arguments(),1,usable_tables); } break; } @@ -360,10 +379,10 @@ void build_funcdeps_for_cond(Table_elimination *te, Item *tmp=new Item_null; if (unlikely(!tmp)) // Should never be true return; - add_funcdep(te, fdeps, *and_level, cond_func, - ((Item_field*)(cond_func->arguments()[0])->real_item())->field, - cond_func->functype() == Item_func::ISNULL_FUNC, - &tmp, 1, usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, + ((Item_field*)(cond_func->arguments()[0])->real_item())->field, + cond_func->functype() == Item_func::ISNULL_FUNC, + &tmp, 1, usable_tables); } break; case Item_func::OPTIMIZE_EQUAL: @@ -380,8 +399,8 @@ void build_funcdeps_for_cond(Table_elimination *te, */ while ((item= it++)) { - add_funcdep(te, fdeps, *and_level, cond_func, item->field, - TRUE, &const_item, 1, usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, item->field, + TRUE, &const_item, 1, usable_tables); } } else @@ -400,8 +419,8 @@ void build_funcdeps_for_cond(Table_elimination *te, { if (!field->eq(item->field)) { - add_funcdep(te, fdeps, *and_level, cond_func, field/*item*/, - TRUE, (Item **) &item, 1, usable_tables); + add_eq_dep(te, fdeps, *and_level, cond_func, field, + TRUE, (Item **) &item, 1, usable_tables); } } it.rewind(); @@ -411,15 +430,19 @@ void build_funcdeps_for_cond(Table_elimination *te, } } + /* - Perform an OR operation on two (adjacent) FUNC_DEP arrays. + Perform an OR operation on two (adjacent) Equality_dep arrays. SYNOPSIS merge_func_deps() + start Start of left OR-part + new_fields Start of right OR-part + end End of right OR-part + and_level AND-level. DESCRIPTION - - This function is invoked for two adjacent arrays of FUNC_DEP elements: + This function is invoked for two adjacent arrays of Equality_dep elements: $LEFT_PART $RIGHT_PART +-----------------------+-----------------------+ @@ -527,17 +550,18 @@ Equality_dep *merge_func_deps(Equality_dep *start, Equality_dep *new_fields, /* - Add a funcdep for a given equality. + Add an Equality_dep element for a given predicate, if applicable + + DESCRIPTION + This function is modeled after add_key_field(). */ static -void add_funcdep(Table_elimination *te, - Equality_dep **eq_dep, uint and_level, - Item_func *cond, Field *field, - bool eq_func, Item **value, - uint num_values, table_map usable_tables) +void add_eq_dep(Table_elimination *te, Equality_dep **eq_dep, + uint and_level, Item_func *cond, Field *field, + bool eq_func, Item **value, uint num_values, + table_map usable_tables) { - // Field *field= item_field->field; if (!(field->table->map & usable_tables)) return; @@ -606,7 +630,11 @@ void add_funcdep(Table_elimination *te, } -Table_dep *get_table_dep(Table_elimination *te, TABLE *table) +/* + Get a Table_dep object for the given table, creating it if necessary. +*/ + +static Table_dep *get_table_dep(Table_elimination *te, TABLE *table) { Table_dep *tbl_dep= new Table_dep(table); Key_dep **key_list= &(tbl_dep->keys); @@ -625,19 +653,21 @@ Table_dep *get_table_dep(Table_elimination *te, TABLE *table) return te->table_deps[table->tablenr] = tbl_dep; } + /* - Given a field, get its dependency element: if it already exists, find it, - otherwise create it. + Get a Field_dep object for the given field, creating it if necessary */ -Field_dep *get_field_dep(Table_elimination *te, Field *field) +static Field_dep *get_field_dep(Table_elimination *te, Field *field) { TABLE *table= field->table; Table_dep *tbl_dep; + /* First, get the table*/ if (!(tbl_dep= te->table_deps[table->tablenr])) tbl_dep= get_table_dep(te, table); - + + /* Try finding the field in field list */ Field_dep **pfield= &(tbl_dep->fields); while (*pfield && (*pfield)->field->field_index < field->field_index) { @@ -646,20 +676,34 @@ Field_dep *get_field_dep(Table_elimination *te, Field *field) if (*pfield && (*pfield)->field->field_index == field->field_index) return *pfield; + /* Create the field and insert it in the list */ Field_dep *new_field= new Field_dep(tbl_dep, field); - new_field->next_table_field= *pfield; *pfield= new_field; + return new_field; } +/* + Create an Outer_join_dep object for the given outer join + + DESCRIPTION + Outer_join_dep objects for children (or further descendants) are always + created before the parents. +*/ + +static Outer_join_dep *get_outer_join_dep(Table_elimination *te, TABLE_LIST *outer_join, table_map deps_map) { Outer_join_dep *oj_dep; oj_dep= new Outer_join_dep(outer_join, deps_map); - + + /* + Collect a bitmap fo tables that we depend on, and also set parent pointer + for descendant outer join elements. + */ Table_map_iterator it(deps_map); int idx; while ((idx= it.next_bit()) != Table_map_iterator::BITMAP_END) @@ -667,6 +711,11 @@ Outer_join_dep *get_outer_join_dep(Table_elimination *te, Table_dep *table_dep; if (!(table_dep= te->table_deps[idx])) { + /* + We get here only when ON expression had no references to inner tables + and Table_map objects weren't created for them. This is a rare/ + unimportant case so it's ok to do not too efficient searches. + */ TABLE *table= NULL; for (TABLE_LIST *tlist= te->join->select_lex->leaf_tables; tlist; tlist=tlist->next_leaf) @@ -680,7 +729,13 @@ Outer_join_dep *get_outer_join_dep(Table_elimination *te, DBUG_ASSERT(table); table_dep= get_table_dep(te, table); } - + + /* + Walk from the table up to its embedding outer joins. The goal is to + find the least embedded outer join nest and set its parent pointer to + point to the newly created Outer_join_dep. + to set the pointer of its near + */ if (!table_dep->outer_join_dep) table_dep->outer_join_dep= oj_dep; else @@ -690,43 +745,35 @@ Outer_join_dep *get_outer_join_dep(Table_elimination *te, oj= oj->parent; oj->parent=oj_dep; } - } return oj_dep; } /* - Perform table elimination in a given join list + Build functional dependency graph for elements of given join list SYNOPSIS collect_funcdeps_for_join_list() - te Table elimination context. - join_list Join list to work on - its_outer_join TRUE <=> the join_list is an inner side of an - outer join - FALSE <=> otherwise (this is top-level join - list, simplify_joins flattens out all - other kinds of join lists) - - tables_in_list Bitmap of tables embedded in the join_list. - tables_used_elsewhere Bitmap of tables that are referred to from - somewhere outside of the join list (e.g. - select list, HAVING, etc). + te Table elimination context. + join_list Join list to work on + build_eq_deps TRUE <=> build Equality_dep elements for all + members of the join list, even if they cannot + be individually eliminated + tables_used_elsewhere Bitmap of tables that are referred to from + somewhere outside of this join list (e.g. + select list, HAVING, ON expressions of parent + joins, etc). + eliminable_tables INOUT Tables that can potentially be eliminated + (needed so we know for which tables to build + dependencies for) + eq_dep INOUT End of array of equality dependencies. DESCRIPTION - Perform table elimination for a join list. - Try eliminating children nests first. - The "all tables in join nest can produce only one matching record - combination" property checking is modeled after constant table detection, - plus we reuse info attempts to eliminate child join nests. - - RETURN - Number of children left after elimination. 0 means everything was - eliminated. + . */ -static uint +static void collect_funcdeps_for_join_list(Table_elimination *te, List *join_list, bool build_eq_deps, @@ -771,7 +818,7 @@ collect_funcdeps_for_join_list(Table_elimination *te, { // build comp_cond from ON expression uint and_level=0; - build_funcdeps_for_cond(te, eq_dep, &and_level, tbl->on_expr, + build_eq_deps_for_cond(te, eq_dep, &and_level, tbl->on_expr, *eliminable_tables); } @@ -781,19 +828,13 @@ collect_funcdeps_for_join_list(Table_elimination *te, tables_used_on_left |= tbl->on_expr->used_tables(); } } - return 0; + return; } + /* - Analyze exising FUNC_DEP array and add elements for tables and uniq keys - - SYNOPSIS - - DESCRIPTION - Add FUNC_DEP elements - - RETURN - . + This is used to analyse expressions in "tbl.col=expr" dependencies so + that we can figure out which fields the expression depends on. */ class Field_dependency_setter : public Field_enumerator @@ -819,20 +860,41 @@ public: return; } } - /* We didn't find the field. Bump the dependency anyway */ + /* + We got here if didn't find this field. It's not a part of + a unique key, and/or there is no field=expr element for it. + Bump the dependency anyway, this will signal that this dependency + cannot be satisfied. + */ te->equality_deps[expr_offset].unknown_args++; } } + Table_elimination *te; - uint expr_offset; /* Offset of the expression we're processing */ + /* Offset of the expression we're processing in the dependency bitmap */ + uint expr_offset; }; +/* + Setup equality dependencies + + SYNOPSIS + setup_equality_deps() + te Table elimination context + bound_deps_list OUT Start of linked list of elements that were found to + be bound (caller will use this to see if that + allows to declare further elements bound) +*/ + static bool setup_equality_deps(Table_elimination *te, Func_dep **bound_deps_list) { DBUG_ENTER("setup_equality_deps"); + /* + Count Field_dep objects and assign each of them a unique bitmap_offset. + */ uint offset= 0; for (Table_dep **tbl_dep=te->table_deps; tbl_dep < te->table_deps + MAX_TABLES; @@ -859,7 +921,10 @@ bool setup_equality_deps(Table_elimination *te, Func_dep **bound_deps_list) bitmap_clear_all(&te->expr_deps); /* - Walk through all field=expr elements and collect all fields. + Analyze all "field=expr" dependencies, and have te->expr_deps encode + dependencies of expressions from fields. + + Also collect a linked list of equalities that are bound. */ Func_dep *bound_dep= NULL; Field_dependency_setter deps_setter(te);