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
This commit is contained in:
parent
d764108a2c
commit
9fa1bce436
@ -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`))))
|
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 view v1, v2;
|
||||||
drop table t0, t1, t2;
|
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;
|
||||||
|
@ -125,3 +125,36 @@ explain extended select id from v2 where attr2 between 12 and 14;
|
|||||||
|
|
||||||
drop view v1, v2;
|
drop view v1, v2;
|
||||||
drop table t0, t1, t2;
|
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;
|
||||||
|
|
||||||
|
@ -42,14 +42,16 @@
|
|||||||
Table elimination is redone on every PS re-execution.
|
Table elimination is redone on every PS re-execution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int
|
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl);
|
||||||
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list,
|
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<TABLE_LIST> *join_list,
|
||||||
|
bool its_outer_join,
|
||||||
|
table_map tables_in_list,
|
||||||
table_map tables_used_elsewhere,
|
table_map tables_used_elsewhere,
|
||||||
uint *const_tbl_count, table_map *const_tables);
|
bool *multiple_matches);
|
||||||
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
|
static bool
|
||||||
extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
|
extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
|
||||||
KEYUSE *key_start, KEYUSE *key_end,
|
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,
|
void eliminate_tables(JOIN *join)
|
||||||
table_map *const_tables)
|
|
||||||
{
|
{
|
||||||
Item *item;
|
Item *item;
|
||||||
table_map used_tables;
|
table_map used_tables;
|
||||||
@ -140,220 +141,140 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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. */
|
/* There are some tables that we probably could eliminate. Try it. */
|
||||||
eliminate_tables_for_join_list(join, join->join_list, used_tables,
|
TABLE *leaves_array[MAX_TABLES];
|
||||||
const_tbl_count, const_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;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Perform table elimination in a given join list
|
Perform table elimination in a given join list
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
eliminate_tables_for_join_list()
|
eliminate_tables_for_list()
|
||||||
join The join
|
join The join
|
||||||
join_list Join list to work on
|
join_list Join list to work on
|
||||||
tables_used_elsewhere Bitmap of tables that are referred to from
|
tables_used_elsewhere Bitmap of tables that are referred to from
|
||||||
somewhere outside of the join list (e.g.
|
somewhere outside of the join list (e.g.
|
||||||
select list, HAVING, etc).
|
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
|
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
|
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 uint
|
||||||
static int
|
eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr,
|
||||||
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list,
|
List<TABLE_LIST> *join_list,
|
||||||
|
bool its_outer_join,
|
||||||
|
table_map tables_in_list,
|
||||||
table_map tables_used_elsewhere,
|
table_map tables_used_elsewhere,
|
||||||
uint *const_tbl_count, table_map *const_tables)
|
bool *multiple_matches)
|
||||||
{
|
{
|
||||||
List_iterator<TABLE_LIST> it(*join_list);
|
|
||||||
table_map used_tables_on_right[MAX_TABLES];
|
|
||||||
table_map tables_used_on_left;
|
|
||||||
TABLE_LIST *tbl;
|
TABLE_LIST *tbl;
|
||||||
int i, n_tables;
|
List_iterator<TABLE_LIST> it(*join_list);
|
||||||
int eliminated=0;
|
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++))
|
while ((tbl= it++))
|
||||||
{
|
{
|
||||||
table_map tables_used_outside= tables_used_on_left |
|
if (tbl->on_expr)
|
||||||
used_tables_on_right[i] |
|
{
|
||||||
tables_used_elsewhere;
|
table_map outside_used_tables= tables_used_elsewhere |
|
||||||
table_map cur_tables= 0;
|
tables_used_on_left;
|
||||||
|
bool multiple_matches= FALSE;
|
||||||
if (tbl->nested_join)
|
if (tbl->nested_join)
|
||||||
{
|
{
|
||||||
DBUG_ASSERT(tbl->on_expr);
|
/* This is "... LEFT JOIN (join_nest) ON cond" */
|
||||||
/*
|
uint n;
|
||||||
There can be cases where table removal is applicable for tables
|
if (!(n= eliminate_tables_for_list(join, cur_table,
|
||||||
within the outer join but not for the outer join itself. Ask to
|
&tbl->nested_join->join_list, TRUE,
|
||||||
remove the children first.
|
tbl->nested_join->used_tables,
|
||||||
|
outside_used_tables,
|
||||||
TODO: NoHopelessEliminationAttempts: the below call can return
|
&multiple_matches)))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
/*
|
mark_as_eliminated(join, tbl);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
tbl->nested_join->n_tables= n;
|
||||||
if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts
|
base_tables += n;
|
||||||
!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
|
else
|
||||||
{
|
{
|
||||||
eliminated += eliminated_in_children;
|
/* 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))
|
||||||
else if (tbl->on_expr)
|
|
||||||
{
|
{
|
||||||
cur_tables= tbl->on_expr->used_tables();
|
mark_as_eliminated(join, tbl);
|
||||||
if (!(tbl->table->map & tables_used_outside) &&
|
}
|
||||||
table_has_one_match(tbl->table, (table_map)-1))
|
else
|
||||||
|
base_tables++;
|
||||||
|
}
|
||||||
|
tables_used_on_left |= tbl->on_expr->used_tables();
|
||||||
|
children_have_multiple_matches= children_have_multiple_matches ||
|
||||||
|
multiple_matches;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
mark_table_as_eliminated(join, tbl->table, const_tbl_count,
|
DBUG_ASSERT(!tbl->nested_join);
|
||||||
const_tables);
|
base_tables++;
|
||||||
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
|
|
||||||
eliminated += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
if (tbl->table)
|
||||||
tables_used_on_left |= cur_tables;
|
*(cur_table++)= tbl->table;
|
||||||
}
|
}
|
||||||
return eliminated;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
*multiple_matches |= children_have_multiple_matches;
|
||||||
|
|
||||||
/*
|
/* Try eliminating the nest we're called for */
|
||||||
Mark table as eliminated:
|
if (its_outer_join && !children_have_multiple_matches &&
|
||||||
- Mark it as constant table
|
!(tables_in_list & tables_used_elsewhere))
|
||||||
- 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));
|
table_map bound_tables= join->const_table_map | (join->all_tables_map() &
|
||||||
tab->type= JT_CONST;
|
~tables_in_list);
|
||||||
join->eliminated_tables |= table->map;
|
table_map old_bound_tables;
|
||||||
*const_tables |= table->map;
|
TABLE **leaves_end= cur_table;
|
||||||
join->const_table_map|= table->map;
|
/*
|
||||||
set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -364,6 +285,9 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count,
|
|||||||
table_has_one_match()
|
table_has_one_match()
|
||||||
table The [base] table being checked
|
table The [base] table being checked
|
||||||
bound_tables Tables that should be considered bound.
|
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
|
DESCRIPTION
|
||||||
Check if table will produce at most one matching record for each record
|
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
|
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;
|
KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
|
||||||
if (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 */
|
do /* For each keypart and each way to read it */
|
||||||
{
|
{
|
||||||
if (keyuse->usable)
|
if (keyuse->usable == 1)
|
||||||
{
|
{
|
||||||
if(!(keyuse->used_tables & ~bound_tables) &&
|
if(!(keyuse->used_tables & ~bound_tables) &&
|
||||||
!(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
|
!(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
|
||||||
@ -516,7 +441,9 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
|
|||||||
{
|
{
|
||||||
if (!(uses[i].dependency_parts & ~bound_parts))
|
if (!(uses[i].dependency_parts & ~bound_parts))
|
||||||
{
|
{
|
||||||
|
table_map old= bound_parts;
|
||||||
bound_parts|= key_part_map(1) << uses[i].keyuse->keypart;
|
bound_parts|= key_part_map(1) << uses[i].keyuse->keypart;
|
||||||
|
if (old != bound_parts)
|
||||||
n_bounded++;
|
n_bounded++;
|
||||||
}
|
}
|
||||||
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
|
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
|
||||||
@ -527,6 +454,42 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
|
|||||||
return FALSE;
|
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<TABLE_LIST> 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)
|
@} (end of group Table_Elimination)
|
||||||
*/
|
*/
|
||||||
|
@ -2653,12 +2653,13 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
|
|||||||
~outer_join, join->select_lex, &sargables))
|
~outer_join, join->select_lex, &sargables))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* Read tables with 0 or 1 rows (system tables) */
|
|
||||||
join->const_table_map= 0;
|
join->const_table_map= 0;
|
||||||
|
join->const_tables= const_count;
|
||||||
|
eliminate_tables(join);
|
||||||
|
const_count= join->const_tables;
|
||||||
|
found_const_table_map= join->const_table_map;
|
||||||
|
|
||||||
eliminate_tables(join, &const_count, &found_const_table_map);
|
/* Read tables with 0 or 1 rows (system tables) */
|
||||||
join->const_table_map= found_const_table_map;
|
|
||||||
|
|
||||||
for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count;
|
for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count;
|
||||||
p_pos < p_end ;
|
p_pos < p_end ;
|
||||||
p_pos++)
|
p_pos++)
|
||||||
@ -2761,7 +2762,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
|
|||||||
{
|
{
|
||||||
start_keyuse=keyuse;
|
start_keyuse=keyuse;
|
||||||
key=keyuse->key;
|
key=keyuse->key;
|
||||||
if (keyuse->usable)
|
if (keyuse->usable == 1)
|
||||||
s->keys.set_bit(key); // QQ: remove this ?
|
s->keys.set_bit(key); // QQ: remove this ?
|
||||||
|
|
||||||
refs=0;
|
refs=0;
|
||||||
@ -2769,7 +2770,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
|
|||||||
eq_part.clear_all();
|
eq_part.clear_all();
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (keyuse->usable && keyuse->val->type() != Item::NULL_ITEM &&
|
if (keyuse->usable==1 && keyuse->val->type() != Item::NULL_ITEM &&
|
||||||
!keyuse->optimize)
|
!keyuse->optimize)
|
||||||
{
|
{
|
||||||
if (!((~found_const_table_map) & keyuse->used_tables))
|
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.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
|
||||||
keyuse.null_rejecting= key_field->null_rejecting;
|
keyuse.null_rejecting= key_field->null_rejecting;
|
||||||
keyuse.cond_guard= key_field->cond_guard;
|
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));
|
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.used_tables=cond_func->key_item()->used_tables();
|
||||||
keyuse.optimize= 0;
|
keyuse.optimize= 0;
|
||||||
keyuse.keypart_map= 0;
|
keyuse.keypart_map= 0;
|
||||||
keyuse.usable= TRUE;
|
keyuse.usable= 1;
|
||||||
VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse));
|
VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3686,7 +3692,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b)
|
|||||||
|
|
||||||
// Usable ones go before the unusable
|
// Usable ones go before the unusable
|
||||||
if (a->usable != b->usable)
|
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
|
// Place const values before other ones
|
||||||
if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) -
|
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;
|
found_eq_constant=0;
|
||||||
for (i=0 ; i < keyuse->elements-1 ; i++,use++)
|
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->optimize != KEY_OPTIMIZE_REF_OR_NULL)
|
||||||
use->table->const_key_parts[use->key]|= use->keypart_map;
|
use->table->const_key_parts[use->key]|= use->keypart_map;
|
||||||
if (use->keypart != FT_KEYPART)
|
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 */
|
/* Save ptr to first use */
|
||||||
if (!use->table->reginfo.join_tab->keyuse)
|
if (!use->table->reginfo.join_tab->keyuse)
|
||||||
use->table->reginfo.join_tab->keyuse=save_pos;
|
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);
|
use->table->reginfo.join_tab->checked_keys.set_bit(use->key);
|
||||||
save_pos++;
|
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.
|
To avoid bad matches, we don't make ref_table_rows less than 100.
|
||||||
*/
|
*/
|
||||||
keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref
|
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 &
|
(map= (keyuse->used_tables & ~join->const_table_map &
|
||||||
~OUTER_REF_TABLE_BIT)))
|
~OUTER_REF_TABLE_BIT)))
|
||||||
{
|
{
|
||||||
@ -4146,7 +4152,7 @@ best_access_path(JOIN *join,
|
|||||||
if 1. expression doesn't refer to forward tables
|
if 1. expression doesn't refer to forward tables
|
||||||
2. we won't get two ref-or-null's
|
2. we won't get two ref-or-null's
|
||||||
*/
|
*/
|
||||||
if (keyuse->usable &&
|
if (keyuse->usable == 1&&
|
||||||
!(remaining_tables & keyuse->used_tables) &&
|
!(remaining_tables & keyuse->used_tables) &&
|
||||||
!(ref_or_null_part && (keyuse->optimize &
|
!(ref_or_null_part && (keyuse->optimize &
|
||||||
KEY_OPTIMIZE_REF_OR_NULL)))
|
KEY_OPTIMIZE_REF_OR_NULL)))
|
||||||
@ -5601,7 +5607,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
|
|||||||
*/
|
*/
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (!(~used_tables & keyuse->used_tables))
|
if (!(~used_tables & keyuse->used_tables) && keyuse->usable == 1)
|
||||||
{
|
{
|
||||||
if (keyparts == keyuse->keypart &&
|
if (keyparts == keyuse->keypart &&
|
||||||
!(found_part_ref_or_null & keyuse->optimize))
|
!(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++)
|
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->usable == 1))
|
||||||
{
|
{
|
||||||
keyuse++; /* Skip other parts */
|
keyuse++; /* Skip other parts */
|
||||||
}
|
}
|
||||||
@ -8985,6 +8991,20 @@ static void restore_prev_nj_state(JOIN_TAB *last)
|
|||||||
JOIN *join= last->join;
|
JOIN *join= last->join;
|
||||||
while (last_emb)
|
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))
|
if (!(--last_emb->nested_join->counter))
|
||||||
join->cur_embedding_map&= ~last_emb->nested_join->nj_map;
|
join->cur_embedding_map&= ~last_emb->nested_join->nj_map;
|
||||||
else if (last_emb->nested_join->n_tables-1 ==
|
else if (last_emb->nested_join->n_tables-1 ==
|
||||||
@ -16685,8 +16705,8 @@ static void print_join(THD *thd,
|
|||||||
|
|
||||||
DBUG_ASSERT(tables->elements >= 1);
|
DBUG_ASSERT(tables->elements >= 1);
|
||||||
/*
|
/*
|
||||||
Assert that the first table in the list isn't eliminated (if it was we
|
Assert that the first table in the list isn't eliminated. This comes from
|
||||||
would have skipped the entire join nest)
|
the fact that the first table can't be inner table of an outer join.
|
||||||
*/
|
*/
|
||||||
DBUG_ASSERT(!eliminated_tables ||
|
DBUG_ASSERT(!eliminated_tables ||
|
||||||
!((*table)->table && ((*table)->table->map & eliminated_tables) ||
|
!((*table)->table && ((*table)->table->map & eliminated_tables) ||
|
||||||
|
@ -57,14 +57,14 @@ typedef struct keyuse_t {
|
|||||||
*/
|
*/
|
||||||
bool *cond_guard;
|
bool *cond_guard;
|
||||||
/*
|
/*
|
||||||
TRUE <=> This keyuse can be used to construct key access.
|
1 <=> This keyuse can be used to construct key access.
|
||||||
FALSE <=> Otherwise. Currently unusable KEYUSEs represent equalities
|
0 <=> Otherwise. Currently unusable KEYUSEs represent equalities
|
||||||
where one table column refers to another one, like this:
|
where one table column refers to another one, like this:
|
||||||
t.keyXpartA=func(t.keyXpartB)
|
t.keyXpartA=func(t.keyXpartB)
|
||||||
This equality cannot be used for index access but is useful
|
This equality cannot be used for index access but is useful
|
||||||
for table elimination.
|
for table elimination.
|
||||||
*/
|
*/
|
||||||
bool usable;
|
int usable;
|
||||||
} KEYUSE;
|
} KEYUSE;
|
||||||
|
|
||||||
class store_key;
|
class store_key;
|
||||||
@ -299,7 +299,12 @@ public:
|
|||||||
fetching data from a cursor
|
fetching data from a cursor
|
||||||
*/
|
*/
|
||||||
bool resume_nested_loop;
|
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. */
|
/* Tables removed by table elimination. Set to 0 before the elimination. */
|
||||||
table_map eliminated_tables;
|
table_map eliminated_tables;
|
||||||
@ -548,6 +553,10 @@ public:
|
|||||||
return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
|
return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
|
||||||
select_lex == unit->fake_select_lex));
|
select_lex == unit->fake_select_lex));
|
||||||
}
|
}
|
||||||
|
inline table_map all_tables_map()
|
||||||
|
{
|
||||||
|
return (table_map(1) << tables) - 1;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
bool make_simple_join(JOIN *join, TABLE *tmp_table);
|
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);
|
return (thd->variables.optimizer_switch & flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void eliminate_tables(JOIN *join, uint *const_tbl_count,
|
void eliminate_tables(JOIN *join);
|
||||||
table_map *const_tables);
|
|
||||||
|
|
||||||
|
@ -1616,7 +1616,10 @@ public:
|
|||||||
typedef struct st_nested_join
|
typedef struct st_nested_join
|
||||||
{
|
{
|
||||||
List<TABLE_LIST> join_list; /* list of elements in the nested join */
|
List<TABLE_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 */
|
table_map not_null_tables; /* tables that rejects nulls */
|
||||||
struct st_join_table *first_nested;/* the first nested table in the plan */
|
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
|
2. check_interleaving_with_nj/restore_prev_nj_state (these are called
|
||||||
by the join optimizer.
|
by the join optimizer.
|
||||||
Before each use the counters are zeroed by reset_nj_counters.
|
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;
|
uint counter;
|
||||||
/* Tables left after elimination */
|
/* Tables left after elimination */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user