1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

BUG#13126: When choosing join order for join with nested joins, don't produce join

orders that cannot be handled by the executioner.


mysql-test/r/bigint.result:
  Added mssing "drop table if exists"
mysql-test/r/join_nested.result:
  Testcase for BUG#13126
mysql-test/t/bigint.test:
  Added mssing "drop table if exists"
mysql-test/t/join_nested.test:
  Testcase for BUG#13126
sql/mysql_priv.h:
  BUG#13126: Added nested_join_map type.
sql/sql_prepare.cc:
  BUG#13126: Don't set NESTED_JOIN::counter to 0 here as it is reset in other place now.
sql/sql_select.cc:
  BUG#13126: When choosing join order for join with nested joins, don't produce join orders
  that the executioner cannot handle. The work is done by check_interleaving_with_nj() and 
  restore_prev_nj_state() functions that are used from the join optimizer to avoid building
  invalid join orders.
sql/sql_select.h:
  BUG#13126: Added JOIN_TAB::embedding_map and JOIN::cur_embedding_map.
sql/table.h:
  BUG#13126: In NESTED_JOIN: added nj_map, added comment about where counter is used.
This commit is contained in:
unknown
2005-10-25 19:28:27 +04:00
parent 11541107b8
commit 2b7f5a45e9
9 changed files with 397 additions and 17 deletions

View File

@ -98,6 +98,12 @@ static COND* substitute_for_best_equal_field(COND *cond,
void *table_join_idx);
static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list,
COND *conds, bool top);
static bool check_interleaving_with_nj(JOIN_TAB *last, JOIN_TAB *next);
static void restore_prev_nj_state(JOIN_TAB *last);
static void reset_nj_counters(List<TABLE_LIST> *join_list);
static uint build_nj_bitmap_for_tables(JOIN *join, List<TABLE_LIST> *join_list,
uint first_unused);
static COND *optimize_cond(JOIN *join, COND *conds,
List<TABLE_LIST> *join_list,
Item::cond_result *cond_value);
@ -522,12 +528,14 @@ bool JOIN::test_in_subselect(Item **where)
return 0;
}
/*
global select optimisation.
return 0 - success
1 - error
error code saved in field 'error'
*/
int
JOIN::optimize()
{
@ -587,6 +595,7 @@ JOIN::optimize()
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(this, join_list, conds, TRUE);
build_nj_bitmap_for_tables(this, join_list, 0);
sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
@ -699,7 +708,8 @@ JOIN::optimize()
DBUG_PRINT("error",("Error: make_select() failed"));
DBUG_RETURN(1);
}
reset_nj_counters(join_list);
make_outerjoin_info(this);
/*
@ -1961,14 +1971,19 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds,
continue;
}
outer_join|= table->map;
s->embedding_map= 0;
for (;embedding; embedding= embedding->embedding)
s->embedding_map|= embedding->nested_join->nj_map;
continue;
}
if (embedding)
{
/* s belongs to a nested join, maybe to several embedded joins */
s->embedding_map= 0;
do
{
NESTED_JOIN *nested_join= embedding->nested_join;
s->embedding_map|=nested_join->nj_map;
s->dependent|= embedding->dep_tables;
embedding= embedding->embedding;
outer_join|= nested_join->used_tables;
@ -3542,6 +3557,8 @@ choose_plan(JOIN *join, table_map join_tables)
bool straight_join= join->select_options & SELECT_STRAIGHT_JOIN;
DBUG_ENTER("choose_plan");
join->cur_embedding_map= 0;
reset_nj_counters(join->join_list);
/*
if (SELECT_STRAIGHT_JOIN option is set)
reorder tables so dependent tables come after tables they depend
@ -4022,7 +4039,9 @@ best_extension_by_limited_search(JOIN *join,
for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
{
table_map real_table_bit= s->table->map;
if ((remaining_tables & real_table_bit) && !(remaining_tables & s->dependent))
if ((remaining_tables & real_table_bit) &&
!(remaining_tables & s->dependent) &&
(!idx || !check_interleaving_with_nj(join->positions[idx-1].table, s)))
{
double current_record_count, current_read_time;
@ -4038,6 +4057,7 @@ best_extension_by_limited_search(JOIN *join,
{
DBUG_EXECUTE("opt", print_plan(join, read_time, record_count, idx,
"prune_by_cost"););
restore_prev_nj_state(s);
continue;
}
@ -4066,6 +4086,7 @@ best_extension_by_limited_search(JOIN *join,
{
DBUG_EXECUTE("opt", print_plan(join, read_time, record_count, idx,
"pruned_by_heuristic"););
restore_prev_nj_state(s);
continue;
}
}
@ -4100,9 +4121,11 @@ best_extension_by_limited_search(JOIN *join,
sizeof(POSITION) * (idx + 1));
join->best_read= current_read_time - 0.001;
}
DBUG_EXECUTE("opt",
print_plan(join, current_read_time, current_record_count, idx, "full_plan"););
DBUG_EXECUTE("opt", print_plan(join, current_read_time,
current_record_count, idx,
"full_plan"););
}
restore_prev_nj_state(s);
}
}
DBUG_VOID_RETURN;
@ -4147,7 +4170,8 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
for (JOIN_TAB **pos=join->best_ref+idx ; (s=*pos) ; pos++)
{
table_map real_table_bit=s->table->map;
if ((rest_tables & real_table_bit) && !(rest_tables & s->dependent))
if ((rest_tables & real_table_bit) && !(rest_tables & s->dependent) &&
(!idx|| !check_interleaving_with_nj(join->positions[idx-1].table, s)))
{
double best,best_time,records;
best=best_time=records=DBL_MAX;
@ -4485,10 +4509,10 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
join->unit->select_limit_cnt >= records)
join->sort_by_table= (TABLE*) 1; // Must use temporary table
/*
/*
Go to the next level only if there hasn't been a better key on
this level! This will cut down the search for a lot simple cases!
*/
*/
double current_record_count=record_count*records;
double current_read_time=read_time+best;
if (best_record_count > current_record_count ||
@ -4509,6 +4533,7 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
return;
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
}
restore_prev_nj_state(s);
if (join->select_options & SELECT_STRAIGHT_JOIN)
break; // Don't test all combinations
}
@ -5094,7 +5119,7 @@ add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab)
This function can be called only after the execution plan
has been chosen.
*/
static void
make_outerjoin_info(JOIN *join)
{
@ -7255,11 +7280,11 @@ propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list,
ascent all attributes are calculated, all outer joins that can be
converted are replaced and then all unnecessary braces are removed.
As join list contains join tables in the reverse order sequential
elimination of outer joins does not requite extra recursive calls.
elimination of outer joins does not require extra recursive calls.
EXAMPLES
Here is an example of a join query with invalid cross references:
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN ON t3.b=t1.b
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b
RETURN VALUE
The new condition, if success
@ -7416,7 +7441,210 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
}
DBUG_RETURN(conds);
}
/*
Assign each nested join structure a bit in nested_join_map
SYNOPSIS
build_nj_bitmap_for_tables()
join Join being processed
join_list List of tables
first_unused Number of first unused bit in nested_join_map before the
call
DESCRIPTION
Assign each nested join structure (except for unary nested joins, see
below) a bit in nested_join_map.
NOTE
This function is called after simplify_joins(), when there are no
redundant nested joins, #non_unary_nested_joins <= #tables_in_join so we
will not run out of bits in nested_join_map.
RETURN
First unused bit in nested_join_map after the call.
*/
static uint build_nj_bitmap_for_tables(JOIN *join, List<TABLE_LIST> *join_list,
uint first_unused)
{
List_iterator<TABLE_LIST> li(*join_list);
TABLE_LIST *table;
DBUG_ENTER("build_nj_bitmap_for_tables");
while ((table= li++))
{
NESTED_JOIN *nested_join;
if ((nested_join= table->nested_join))
{
/*
It is guaranteed by simplify_joins() function that a nested join
that has only one child represents a single table VIEW (and a child
is an underlying table). We don't assign a bit to the nested join
because
1. it is redundant (a "sequence" of one table cannot be interleaved
with anything)
2. we could run out bits in nested_join_map otherwise.
*/
if (nested_join->join_list.elements != 1)
{
nested_join->nj_map= 1 << first_unused++;
first_unused= build_nj_bitmap_for_tables(join, &nested_join->join_list,
first_unused);
}
}
}
DBUG_RETURN(first_unused);
}
/*
Set NESTED_JOIN::counter=0 in all nested joins in passed list
SYNOPSIS
reset_nj_counters()
join_list List of nested joins to process. It may also contain base
tables which will be ignored.
DESCRIPTION
Recursively set NESTED_JOIN::counter=0 for all nested joins contained in
the passed join_list.
*/
static void reset_nj_counters(List<TABLE_LIST> *join_list)
{
List_iterator<TABLE_LIST> li(*join_list);
TABLE_LIST *table;
DBUG_ENTER("reset_nj_counters");
while ((table= li++))
{
NESTED_JOIN *nested_join;
if ((nested_join= table->nested_join))
{
nested_join->counter= 0;
reset_nj_counters(&nested_join->join_list);
}
}
DBUG_VOID_RETURN;
}
/*
Check interleaving with an inner tables of an outer join for extension table
SYNOPSIS
check_interleaving_with_nj()
join Join being processed
last_tab Last table in current partial join order (this function is
not called for empty partial join orders)
next_tab Table we're going to extend the current partial join with
DESCRIPTION
Check if table next_tab can be added to current partial join order, and
if yes, record that it has been added.
The function assumes that both current partial join order and its
extension with next_tab are valid wrt table dependencies.
NOTE
The nested joins aspect of information about current partial join order is
stored in join->cur_embedding_map and
{each_join_table}->pos_in_table_list (->embedding)+ ->nested_join->counter
Joins optimizer calls check_interleaving_with_nj() and
restore_prev_nj_state() to update this info and avoid constructing invalid
join orders. There is no need for any initialization of
{each_join_table}->...->counter before the join optimizer run.
IMPLEMENTATION
The nested [outer] joins executioner algorithm imposes these limitations
on join order:
1. "Outer tables first" - any "outer" table must be before any
corresponding "inner" table.
2. "No interleaving" - tables inside a nested join must form a continuous
sequence in join order (i.e. the sequence must not be interrupted by
tables that are outside of this nested join).
#1 is checked elsewhere, this function checks #2 provided that #1 has been
already checked.
WHY NEED NON-INTERLEAVING
Consider an example:
select * from t0 join t1 left join (t2 join t3) on cond1
The join order "t1 t2 t0 t3" is invalid:
table t0 is outside of the nested join, so WHERE condition for t0 is
attached directly to t0 (without triggers, and it may be used to access
t0). Applying WHERE(t0) to (t2,t0,t3) record is invalid as we may miss
combinations of (t1, t2, t3) that satisfy condition cond1, and produce a
null-complemented (t1, t2.NULLs, t3.NULLs) row, which should not have
been produced.
If table t0 is not between t2 and t3, the problem doesn't exist:
* If t0 is located after (t2,t3), WHERE(t0) is applied after nested join
processing has finished.
* If t0 is located before (t2,t3), predicates like WHERE_cond(t0, t2) are
wrapped into condition triggers, which takes care of correct nested
join processing.
RETURN
FALSE Join order extended, nested joins info about current join order
(see NOTE section) updated.
TRUE Requested join order extension not allowed.
*/
static bool check_interleaving_with_nj(JOIN_TAB *last, JOIN_TAB *next)
{
TABLE_LIST *next_emb= next->table->pos_in_table_list->embedding;
JOIN *join= last->join;
if (join->cur_embedding_map & ~next->embedding_map)
return TRUE;
for (;next_emb; next_emb= next_emb->embedding)
{
next_emb->nested_join->counter++;
if (next_emb->nested_join->counter == 1)
{
/* next_emb is a first table inside a nested join */
join->cur_embedding_map |= next_emb->nested_join->nj_map;
}
if (next_emb->nested_join->join_list.elements !=
next_emb->nested_join->counter)
break;
join->cur_embedding_map &= ~next_emb->nested_join->nj_map;
}
return FALSE;
}
/*
Nested joins perspective: Remove the last table from the join order
SYNOPSIS
restore_prev_nj_state()
last join table to remove, it is assumed to be the last in current
partial join order.
DESCRIPTION
Remove the last table from the partial join order and update the nested
joins counters and join->cur_embedding_map. It is ok to call this
function for the first table in join order (for which
check_interleaving_with_nj has not been called)
*/
static void restore_prev_nj_state(JOIN_TAB *last)
{
TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding;
JOIN *join= last->join;
while (last_emb && !(--last_emb->nested_join->counter))
{
join->cur_embedding_map &= last_emb->nested_join->nj_map;
last_emb= last_emb->embedding;
}
}
static COND *
optimize_cond(JOIN *join, COND *conds, List<TABLE_LIST> *join_list,