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
This commit is contained in:
parent
defbdce7e8
commit
4102605fba
@ -1905,3 +1905,4 @@ sql/share/swedish
|
|||||||
sql/share/ukrainian
|
sql/share/ukrainian
|
||||||
libmysqld/examples/mysqltest.cc
|
libmysqld/examples/mysqltest.cc
|
||||||
extra/libevent/event-config.h
|
extra/libevent/event-config.h
|
||||||
|
libmysqld/opt_table_elimination.cc
|
||||||
|
@ -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 \
|
rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \
|
||||||
sql_tablespace.cc \
|
sql_tablespace.cc \
|
||||||
rpl_injector.cc my_user.c partition_info.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)
|
libmysqld_int_a_SOURCES= $(libmysqld_sources)
|
||||||
nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
|
nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
|
||||||
|
@ -73,7 +73,7 @@ ADD_EXECUTABLE(mysqld
|
|||||||
partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc
|
partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc
|
||||||
rpl_rli.cc rpl_mi.cc sql_servers.cc
|
rpl_rli.cc rpl_mi.cc sql_servers.cc
|
||||||
sql_connect.cc scheduler.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.cc
|
||||||
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
|
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
|
||||||
${PROJECT_SOURCE_DIR}/include/mysqld_error.h
|
${PROJECT_SOURCE_DIR}/include/mysqld_error.h
|
||||||
|
@ -121,7 +121,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
|
|||||||
event_queue.cc event_db_repository.cc events.cc \
|
event_queue.cc event_db_repository.cc events.cc \
|
||||||
sql_plugin.cc sql_binlog.cc \
|
sql_plugin.cc sql_binlog.cc \
|
||||||
sql_builtin.cc sql_tablespace.cc partition_info.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
|
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c
|
||||||
|
|
||||||
|
16
sql/item.cc
16
sql/item.cc
@ -1915,17 +1915,22 @@ void Item_field::reset_field(Field *f)
|
|||||||
name= (char*) f->field_name;
|
name= (char*) f->field_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Item_field::check_column_usage_processor(uchar *arg)
|
bool Item_field::check_column_usage_processor(uchar *arg)
|
||||||
{
|
{
|
||||||
Field_processor_info* info=(Field_processor_info*)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)
|
if (used_tables() & ~info->allowed_tables)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (field->table == info->table)
|
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)))
|
if (!(field->part_of_key.is_set(info->keyno)))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
|
/* Find which key part we're using and mark it in needed_key_parts */
|
||||||
KEY *key= &field->table->key_info[info->keyno];
|
KEY *key= &field->table->key_info[info->keyno];
|
||||||
for (uint part= 0; part < key->key_parts; part++)
|
for (uint part= 0; part < key->key_parts; part++)
|
||||||
{
|
{
|
||||||
@ -1935,10 +1940,17 @@ bool Item_field::check_column_usage_processor(uchar *arg)
|
|||||||
break;
|
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
|
const char *Item_ident::full_name() const
|
||||||
{
|
{
|
||||||
char *tmp;
|
char *tmp;
|
||||||
|
@ -731,7 +731,11 @@ public:
|
|||||||
virtual bool val_bool_result() { return val_bool(); }
|
virtual bool val_bool_result() { return val_bool(); }
|
||||||
virtual bool is_null_result() { return is_null(); }
|
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; }
|
virtual table_map used_tables() const { return (table_map) 0L; }
|
||||||
/*
|
/*
|
||||||
Return table map of tables that can't be NULL tables (tables that are
|
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);
|
bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Data for Item::check_column_usage_processor */
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
table_map allowed_tables;
|
table_map allowed_tables;
|
||||||
@ -1022,6 +1026,7 @@ typedef struct
|
|||||||
uint needed_key_parts;
|
uint needed_key_parts;
|
||||||
} Field_processor_info;
|
} Field_processor_info;
|
||||||
|
|
||||||
|
|
||||||
class sp_head;
|
class sp_head;
|
||||||
|
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery,
|
|||||||
if (lex->having && (lex->having)->walk(processor, walk_subquery,
|
if (lex->having && (lex->having)->walk(processor, walk_subquery,
|
||||||
argument))
|
argument))
|
||||||
return 1;
|
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++))
|
while ((item=li++))
|
||||||
{
|
{
|
||||||
|
@ -351,9 +351,10 @@ public:
|
|||||||
Return bitmap of tables that are needed to evaluate the item.
|
Return bitmap of tables that are needed to evaluate the item.
|
||||||
|
|
||||||
The implementation takes into account the used strategy: items resolved
|
The implementation takes into account the used strategy: items resolved
|
||||||
at optimization phase report 0.
|
at optimization phase will report 0.
|
||||||
Items that depend on the number of rows only, e.g. COUNT(*) will report
|
Items that depend on the number of join output records, but not columns
|
||||||
zero, but will still false from const_item().
|
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; }
|
table_map used_tables() const { return used_tables_cache; }
|
||||||
void update_used_tables ();
|
void update_used_tables ();
|
||||||
|
493
sql/opt_table_elimination.cc
Normal file
493
sql/opt_table_elimination.cc
Normal file
@ -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<TABLE_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<Item> 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<Item> 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<TABLE_LIST> *join_list,
|
||||||
|
table_map used_tables_elsewhere,
|
||||||
|
uint *const_tbl_count, table_map *const_tables)
|
||||||
|
{
|
||||||
|
List_iterator<TABLE_LIST> 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<TABLE_LIST> 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<keyuse, key_parts_it_depends_on>*/
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
@ -42,11 +42,6 @@
|
|||||||
#define TMP_ENGINE_HTON myisam_hton
|
#define TMP_ENGINE_HTON myisam_hton
|
||||||
#endif
|
#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",
|
const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
|
||||||
"MAYBE_REF","ALL","range","index","fulltext",
|
"MAYBE_REF","ALL","range","index","fulltext",
|
||||||
"ref_or_null","unique_subquery","index_subquery",
|
"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,
|
table_map table_map, SELECT_LEX *select_lex,
|
||||||
st_sargable_param **sargables);
|
st_sargable_param **sargables);
|
||||||
static int sort_keyuse(KEYUSE *a,KEYUSE *b);
|
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,
|
static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
|
||||||
table_map used_tables);
|
table_map used_tables);
|
||||||
static bool choose_plan(JOIN *join,table_map join_tables);
|
static bool choose_plan(JOIN *join,table_map join_tables);
|
||||||
@ -2386,10 +2380,13 @@ mysql_select(THD *thd, Item ***rref_pointer_array,
|
|||||||
}
|
}
|
||||||
else
|
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)
|
if (select_options & SELECT_DESCRIBE)
|
||||||
free_join= 0;
|
free_join= 0;
|
||||||
// }psergey
|
|
||||||
if (!(join= new JOIN(thd, fields, select_options, result)))
|
if (!(join= new JOIN(thd, fields, select_options, result)))
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
thd_proc_info(thd, "init");
|
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 */
|
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<TABLE_LIST> *join_list,
|
|
||||||
table_map used_tables_elsewhere,
|
|
||||||
uint *const_tbl_count, table_map *const_tables)
|
|
||||||
{
|
|
||||||
List_iterator<TABLE_LIST> 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<TABLE_LIST> 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<Item> 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<Item> 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
|
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 */
|
/* Calc how many (possible) matched records in each table */
|
||||||
|
|
||||||
for (s=stat ; s < stat_end ; s++)
|
for (s=stat ; s < stat_end ; s++)
|
||||||
@ -3354,7 +2970,7 @@ typedef struct key_field_t {
|
|||||||
*/
|
*/
|
||||||
bool null_rejecting;
|
bool null_rejecting;
|
||||||
bool *cond_guard; /* See KEYUSE::cond_guard */
|
bool *cond_guard; /* See KEYUSE::cond_guard */
|
||||||
bool usable;
|
bool usable; /* See KEYUSE::usable */
|
||||||
} KEY_FIELD;
|
} KEY_FIELD;
|
||||||
|
|
||||||
|
|
||||||
@ -4428,8 +4044,7 @@ add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab)
|
|||||||
|
|
||||||
/** Save const tables first as used tables. */
|
/** Save const tables first as used tables. */
|
||||||
|
|
||||||
static void
|
void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
|
||||||
set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
|
|
||||||
{
|
{
|
||||||
join->positions[idx].table= table;
|
join->positions[idx].table= table;
|
||||||
join->positions[idx].key=key;
|
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;
|
unit->fake_select_lex->options|= SELECT_DESCRIBE;
|
||||||
if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
|
if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
|
||||||
res= unit->exec();
|
res= unit->exec();
|
||||||
//psergey-move: res|= unit->cleanup();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,11 @@
|
|||||||
#include "procedure.h"
|
#include "procedure.h"
|
||||||
#include <myisam.h>
|
#include <myisam.h>
|
||||||
|
|
||||||
|
#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 {
|
typedef struct keyuse_t {
|
||||||
TABLE *table;
|
TABLE *table;
|
||||||
Item *val; /**< or value if no field */
|
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)
|
NULL - Otherwise (the source equality can't be turned off)
|
||||||
*/
|
*/
|
||||||
bool *cond_guard;
|
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;
|
bool usable;
|
||||||
} KEYUSE;
|
} KEYUSE;
|
||||||
|
|
||||||
@ -734,9 +747,13 @@ bool error_if_full_join(JOIN *join);
|
|||||||
int report_error(TABLE *table, int error);
|
int report_error(TABLE *table, int error);
|
||||||
int safe_index_read(JOIN_TAB *tab);
|
int safe_index_read(JOIN_TAB *tab);
|
||||||
COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value);
|
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)
|
inline bool optimizer_flag(THD *thd, uint flag)
|
||||||
{
|
{
|
||||||
return (thd->variables.optimizer_switch & flag);
|
return (thd->variables.optimizer_switch & flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void eliminate_tables(JOIN *join, uint *const_tbl_count,
|
||||||
|
table_map *const_tables);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user