Skip to content

Commit 19c7216

Browse files
committed
MDEV-28749: restore_prev_nj_state() doesn't update cur_sj_inner_tables correctly
(Try 2) (Cherry-pick back into 10.3) The code that updates semi-join optimization state for a join order prefix had several bugs. The visible effect was bad optimization for FirstMatch or LooseScan strategies: they either weren't considered when they should have been, or considered when they shouldn't have been. In order to hit the bug, the optimizer needs to consider several different join prefixes in a certain order. Queries with "obvious" query plans which prune all join orders except one are not affected. Internally, the bugs in updates of semi-join state were: 1. restore_prev_sj_state() assumed that "we assume remaining_tables doesnt contain @tab" which wasn't true. 2. Another bug in this function: it did remove bits from join->cur_sj_inner_tables but never added them. 3. greedy_search() adds tables into the join prefix but neglects to update the semi-join optimization state. (It does update nested outer join state, see this call: check_interleaving_with_nj(best_table) but there's no matching call to update the semi-join state. (This wasn't visible because most of the state is in the POSITION structure which is updated. But there is also state in JOIN, too) The patch: - Fixes all of the above - Adds JOIN::dbug_verify_sj_inner_tables() which is used to verify the state is correct at every step. - Renames advance_sj_state() to optimize_semi_joins(). = Introduces update_sj_state() which ideally should have been called "advance_sj_state" but I didn't reuse the name to not create confusion.
1 parent 392e744 commit 19c7216

File tree

6 files changed

+118
-33
lines changed

6 files changed

+118
-33
lines changed

mysql-test/main/subselect_sj.result

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,10 +2178,10 @@ INSERT INTO t5 VALUES (7,0),(9,0);
21782178
explain
21792179
SELECT * FROM t3 WHERE t3.a IN (SELECT t5.a FROM t2, t4, t5 WHERE t2.c = t5.a AND t2.b = t5.b);
21802180
id select_type table type possible_keys key key_len ref rows Extra
2181-
1 PRIMARY t5 index a a 10 NULL 2 Using index; Start temporary
2181+
1 PRIMARY t5 index a a 10 NULL 2 Using where; Using index; LooseScan
21822182
1 PRIMARY t4 ALL NULL NULL NULL NULL 3
2183-
1 PRIMARY t2 ALL b NULL NULL NULL 10 Using where
2184-
1 PRIMARY t3 ALL NULL NULL NULL NULL 15 Using where; End temporary; Using join buffer (flat, BNL join)
2183+
1 PRIMARY t2 ref b b 5 test.t5.b 2 Using where; FirstMatch(t5)
2184+
1 PRIMARY t3 ALL NULL NULL NULL NULL 15 Using where; Using join buffer (flat, BNL join)
21852185
SELECT * FROM t3 WHERE t3.a IN (SELECT t5.a FROM t2, t4, t5 WHERE t2.c = t5.a AND t2.b = t5.b);
21862186
a
21872187
0

mysql-test/main/subselect_sj_jcl6.result

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,10 +2189,10 @@ INSERT INTO t5 VALUES (7,0),(9,0);
21892189
explain
21902190
SELECT * FROM t3 WHERE t3.a IN (SELECT t5.a FROM t2, t4, t5 WHERE t2.c = t5.a AND t2.b = t5.b);
21912191
id select_type table type possible_keys key key_len ref rows Extra
2192-
1 PRIMARY t5 index a a 10 NULL 2 Using index; Start temporary
2193-
1 PRIMARY t4 ALL NULL NULL NULL NULL 3 Using join buffer (flat, BNL join)
2194-
1 PRIMARY t2 ALL b NULL NULL NULL 10 Using where; Using join buffer (incremental, BNL join)
2195-
1 PRIMARY t3 ALL NULL NULL NULL NULL 15 Using where; End temporary; Using join buffer (incremental, BNL join)
2192+
1 PRIMARY t5 index a a 10 NULL 2 Using where; Using index; LooseScan
2193+
1 PRIMARY t4 ALL NULL NULL NULL NULL 3
2194+
1 PRIMARY t2 ref b b 5 test.t5.b 2 Using where; FirstMatch(t5)
2195+
1 PRIMARY t3 ALL NULL NULL NULL NULL 15 Using where; Using join buffer (flat, BNL join)
21962196
SELECT * FROM t3 WHERE t3.a IN (SELECT t5.a FROM t2, t4, t5 WHERE t2.c = t5.a AND t2.b = t5.b);
21972197
a
21982198
0

sql/opt_subselect.cc

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@
179179
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
180180
- optimize_semijoin_nests() does pre-optimization
181181
- during join optimization, the join has one JOIN_TAB (or is it POSITION?)
182-
array, and suffix-based detection is used, see advance_sj_state()
182+
array, and suffix-based detection is used, see optimize_semi_joins()
183183
- after join optimization is done, get_best_combination() switches
184184
the data-structure to prefix-based, multiple JOIN_TAB ranges format.
185185
@@ -2655,7 +2655,7 @@ bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables)
26552655
Do semi-join optimization step after we've added a new tab to join prefix
26562656
26572657
SYNOPSIS
2658-
advance_sj_state()
2658+
optimize_semi_joins()
26592659
join The join we're optimizing
26602660
remaining_tables Tables not in the join prefix
26612661
new_join_tab Join tab we've just added to the join prefix
@@ -2715,9 +2715,9 @@ bool is_multiple_semi_joins(JOIN *join, POSITION *prefix, uint idx, table_map in
27152715
}
27162716

27172717

2718-
void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx,
2719-
double *current_record_count, double *current_read_time,
2720-
POSITION *loose_scan_pos)
2718+
void optimize_semi_joins(JOIN *join, table_map remaining_tables, uint idx,
2719+
double *current_record_count,
2720+
double *current_read_time, POSITION *loose_scan_pos)
27212721
{
27222722
POSITION *pos= join->positions + idx;
27232723
const JOIN_TAB *new_join_tab= pos->table;
@@ -2876,19 +2876,36 @@ void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx,
28762876
}
28772877
}
28782878

2879-
if ((emb_sj_nest= new_join_tab->emb_sj_nest))
2879+
update_sj_state(join, new_join_tab, idx, remaining_tables);
2880+
2881+
pos->prefix_cost.convert_from_cost(*current_read_time);
2882+
pos->prefix_record_count= *current_record_count;
2883+
pos->dups_producing_tables= dups_producing_tables;
2884+
}
2885+
2886+
2887+
/*
2888+
Update JOIN's semi-join optimization state after the join tab new_tab
2889+
has been added into the join prefix.
2890+
2891+
@seealso restore_prev_sj_state() does the reverse actoion
2892+
*/
2893+
2894+
void update_sj_state(JOIN *join, const JOIN_TAB *new_tab,
2895+
uint idx, table_map remaining_tables)
2896+
{
2897+
if (TABLE_LIST *emb_sj_nest= new_tab->emb_sj_nest)
28802898
{
28812899
join->cur_sj_inner_tables |= emb_sj_nest->sj_inner_tables;
28822900

28832901
/* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
28842902
if (!(remaining_tables &
2885-
emb_sj_nest->sj_inner_tables & ~new_join_tab->table->map))
2903+
emb_sj_nest->sj_inner_tables & ~new_tab->table->map))
28862904
join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
28872905
}
2888-
2889-
pos->prefix_cost.convert_from_cost(*current_read_time);
2890-
pos->prefix_record_count= *current_record_count;
2891-
pos->dups_producing_tables= dups_producing_tables;
2906+
#ifndef DBUG_OFF
2907+
join->dbug_verify_sj_inner_tables(idx + 1);
2908+
#endif
28922909
}
28932910

28942911

@@ -3402,10 +3419,45 @@ bool Duplicate_weedout_picker::check_qep(JOIN *join,
34023419
return FALSE;
34033420
}
34043421

3422+
#ifndef DBUG_OFF
3423+
/*
3424+
Verify the value of JOIN::cur_sj_inner_tables by recomputing it
3425+
*/
3426+
void JOIN::dbug_verify_sj_inner_tables(uint prefix_size) const
3427+
{
3428+
table_map cur_map= const_table_map;
3429+
table_map nests_entered= 0;
3430+
if (emb_sjm_nest)
3431+
{
3432+
DBUG_ASSERT(cur_sj_inner_tables == 0);
3433+
return;
3434+
}
3435+
3436+
for (uint i= const_tables; i < prefix_size; i++)
3437+
{
3438+
JOIN_TAB *tab= positions[i].table;
3439+
cur_map |= tab->table->map;
3440+
if (TABLE_LIST *sj_nest= tab->emb_sj_nest)
3441+
{
3442+
nests_entered |= sj_nest->sj_inner_tables;
3443+
if (!(sj_nest->sj_inner_tables & ~cur_map))
3444+
{
3445+
// all nest tables are in the prefix already
3446+
nests_entered &= ~sj_nest->sj_inner_tables;
3447+
}
3448+
}
3449+
}
3450+
DBUG_ASSERT(nests_entered == cur_sj_inner_tables);
3451+
}
3452+
#endif
34053453

34063454
/*
34073455
Remove the last join tab from from join->cur_sj_inner_tables bitmap
3408-
we assume remaining_tables doesnt contain @tab.
3456+
3457+
@note
3458+
remaining_tables contains @tab.
3459+
3460+
@seealso update_sj_state() does the reverse
34093461
*/
34103462

34113463
void restore_prev_sj_state(const table_map remaining_tables,
@@ -3419,15 +3471,30 @@ void restore_prev_sj_state(const table_map remaining_tables,
34193471
tab->join->sjm_lookup_tables &= ~subq_tables;
34203472
}
34213473

3422-
if ((emb_sj_nest= tab->emb_sj_nest))
3474+
if (!tab->join->emb_sjm_nest && (emb_sj_nest= tab->emb_sj_nest))
34233475
{
3476+
table_map subq_tables= emb_sj_nest->sj_inner_tables &
3477+
~tab->join->const_table_map;
34243478
/* If we're removing the last SJ-inner table, remove the sj-nest */
3425-
if ((remaining_tables & emb_sj_nest->sj_inner_tables) ==
3426-
(emb_sj_nest->sj_inner_tables & ~tab->table->map))
3479+
if ((remaining_tables & subq_tables) == subq_tables)
34273480
{
3481+
// All non-const tables of the SJ nest are in the remaining_tables.
3482+
// we are not in the nest anymore.
34283483
tab->join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
34293484
}
3485+
else
3486+
{
3487+
// Semi-join nest has:
3488+
// - a table being removed (not in the prefix)
3489+
// - some tables in the prefix.
3490+
tab->join->cur_sj_inner_tables |= emb_sj_nest->sj_inner_tables;
3491+
}
34303492
}
3493+
3494+
#ifndef DBUG_OFF
3495+
/* positions[idx] has been removed. Verify the state for [0...idx-1] */
3496+
tab->join->dbug_verify_sj_inner_tables(idx);
3497+
#endif
34313498
}
34323499

34333500

@@ -3636,8 +3703,8 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join)
36363703
join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
36373704
join->best_positions[first].n_sj_tables= sjm->tables;
36383705
/*
3639-
Do what advance_sj_state did: re-run best_access_path for every table
3640-
in the [last_inner_table + 1; pos..) range
3706+
Do what optimize_semi_joins did: re-run best_access_path for every
3707+
table in the [last_inner_table + 1; pos..) range
36413708
*/
36423709
double prefix_rec_count;
36433710
/* Get the prefix record count */
@@ -4842,7 +4909,7 @@ int setup_semijoin_loosescan(JOIN *join)
48424909
48434910
48444911
The choice between the strategies is made by the join optimizer (see
4845-
advance_sj_state() and fix_semijoin_strategies_for_picked_join_order()).
4912+
optimize_semi_joins() and fix_semijoin_strategies_for_picked_join_order()).
48464913
This function sets up all fields/structures/etc needed for execution except
48474914
for setup/initialization of semi-join materialization which is done in
48484915
setup_sj_materialization() (todo: can't we move that to here also?)

sql/opt_subselect.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,11 @@ class Loose_scan_opt
310310
};
311311

312312

313-
void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx,
314-
double *current_record_count, double *current_read_time,
315-
POSITION *loose_scan_pos);
313+
void optimize_semi_joins(JOIN *join, table_map remaining_tables, uint idx,
314+
double *current_record_count,
315+
double *current_read_time, POSITION *loose_scan_pos);
316+
void update_sj_state(JOIN *join, const JOIN_TAB *new_tab,
317+
uint idx, table_map remaining_tables);
316318
void restore_prev_sj_state(const table_map remaining_tables,
317319
const JOIN_TAB *tab, uint idx);
318320

sql/sql_select.cc

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7721,6 +7721,10 @@ choose_plan(JOIN *join, table_map join_tables)
77217721
{
77227722
choose_initial_table_order(join);
77237723
}
7724+
/*
7725+
Note: constant tables are already in the join prefix. We don't
7726+
put them into the cur_sj_inner_tables, though.
7727+
*/
77247728
join->cur_sj_inner_tables= 0;
77257729

77267730
if (straight_join)
@@ -8023,8 +8027,8 @@ optimize_straight_join(JOIN *join, table_map join_tables)
80238027
read_time= COST_ADD(read_time,
80248028
COST_ADD(join->positions[idx].read_time,
80258029
record_count / (double) TIME_FOR_COMPARE));
8026-
advance_sj_state(join, join_tables, idx, &record_count, &read_time,
8027-
&loose_scan_pos);
8030+
optimize_semi_joins(join, join_tables, idx, &record_count, &read_time,
8031+
&loose_scan_pos);
80288032

80298033
join_tables&= ~(s->table->map);
80308034
double pushdown_cond_selectivity= 1.0;
@@ -8201,6 +8205,12 @@ greedy_search(JOIN *join,
82018205
/* This has been already checked by best_extension_by_limited_search */
82028206
DBUG_ASSERT(!is_interleave_error);
82038207

8208+
/*
8209+
Also, update the semi-join optimization state. Information about the
8210+
picked semi-join operation is in best_pos->...picker, but we need to
8211+
update the global state in the JOIN object, too.
8212+
*/
8213+
update_sj_state(join, best_table, idx, remaining_tables);
82048214

82058215
/* find the position of 'best_table' in 'join->best_ref' */
82068216
best_idx= idx;
@@ -8983,8 +8993,8 @@ best_extension_by_limited_search(JOIN *join,
89838993
current_record_count /
89848994
(double) TIME_FOR_COMPARE));
89858995

8986-
advance_sj_state(join, remaining_tables, idx, &current_record_count,
8987-
&current_read_time, &loose_scan_pos);
8996+
optimize_semi_joins(join, remaining_tables, idx, &current_record_count,
8997+
&current_read_time, &loose_scan_pos);
89888998

89898999
/* Expand only partial plans with lower cost than the best QEP so far */
89909000
if (current_read_time >= join->best_read)

sql/sql_select.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1259,9 +1259,15 @@ class JOIN :public Sql_alloc
12591259
Bitmap of inner tables of semi-join nests that have a proper subset of
12601260
their tables in the current join prefix. That is, of those semi-join
12611261
nests that have their tables both in and outside of the join prefix.
1262+
(Note: tables that are constants but have not been pulled out of semi-join
1263+
nests are not considered part of semi-join nests)
12621264
*/
12631265
table_map cur_sj_inner_tables;
1264-
1266+
1267+
#ifndef DBUG_OFF
1268+
void dbug_verify_sj_inner_tables(uint n_positions) const;
1269+
#endif
1270+
12651271
/* We also maintain a stack of join optimization states in * join->positions[] */
12661272
/******* Join optimization state members end *******/
12671273

0 commit comments

Comments
 (0)