Skip to content

Commit 68432a0

Browse files
committed
MDEV-29638 Crash when considering Split-Materialized plan
Variant 2: "Don't call optimize_stage2 too early" The affected query had Split-Materialized derived table in another derived table: select * -- select#1 from ( select * -- select#2 from t1, (select * from t2 ... group by t2.group_col) DT -- select#3 where t1.col=t2.group_col ) TBL; The optimization went as follows: JOIN::optimize() calls select_lex->handle_derived(DT_OPTIMIZE) which calls JOIN::optimize() for all (direct and indirect) children SELECTs. select#1->handle_derived() calls JOIN::optimize() for select#2 and #3; select#2->handle_derived() calls JOIN::optimize() for select#3 the second time. That call would call JOIN::optimize_stage2(), assuming the query plan choice has been finalized for select#3. But after that, JOIN::optimize() for select#2 would continue and consider Split-Materialized for select#3. This would attempt to pick another join order and cause a crash. The fix is to have JOIN::optimize() not call optimize_stage2() ever. Finalizing the query plan choice is now done by a separate call: select_lex->handle_derived(thd->lex, DT_OPTIMIZE_STAGE2) which invokes JOIN::optimize_stage2() and saves the query plan.
1 parent 99a862c commit 68432a0

File tree

6 files changed

+143
-13
lines changed

6 files changed

+143
-13
lines changed

mysql-test/main/derived_split_innodb.result

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,5 +1045,27 @@ WHERE t1.a LIKE 'B%';
10451045
a
10461046
DROP TABLE t1,t2;
10471047
SET optimizer_switch= @save_optimizer_switch;
1048+
#
1049+
# MDEV-29638 Crash when considering Split-Materialized plan
1050+
#
1051+
set @save_optimizer_switch= @@optimizer_switch;
1052+
set optimizer_switch='condition_pushdown_for_derived=off,split_materialized=on';
1053+
CREATE TABLE t1 (id int PRIMARY KEY)engine=innodb;
1054+
CREATE TABLE t2 (id int PRIMARY KEY, c int) engine=innodb;
1055+
CREATE TABLE t3 (id int PRIMARY KEY, a int , b int, KEY (a))engine=innodb;
1056+
SELECT * FROM
1057+
(
1058+
SELECT DISTINCT t1.id
1059+
FROM t1 JOIN
1060+
(
1061+
SELECT t2.id FROM t2 JOIN t3
1062+
ON t3.id = t2.c
1063+
WHERE (t3.a > 2 AND t3.b = 2)
1064+
GROUP BY t2.id
1065+
) m2 ON m2.id = t1.id
1066+
) dt;
1067+
id
1068+
drop table t1, t2, t3;
1069+
SET optimizer_switch= @save_optimizer_switch;
10481070
# End of 10.11 tests
10491071
SET GLOBAL innodb_stats_persistent=@save_innodb_stats_persistent;

mysql-test/main/derived_split_innodb.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,5 +664,32 @@ eval $q;
664664
DROP TABLE t1,t2;
665665

666666
SET optimizer_switch= @save_optimizer_switch;
667+
668+
--echo #
669+
--echo # MDEV-29638 Crash when considering Split-Materialized plan
670+
--echo #
671+
672+
set @save_optimizer_switch= @@optimizer_switch;
673+
set optimizer_switch='condition_pushdown_for_derived=off,split_materialized=on';
674+
675+
CREATE TABLE t1 (id int PRIMARY KEY)engine=innodb;
676+
CREATE TABLE t2 (id int PRIMARY KEY, c int) engine=innodb;
677+
CREATE TABLE t3 (id int PRIMARY KEY, a int , b int, KEY (a))engine=innodb;
678+
679+
SELECT * FROM
680+
(
681+
SELECT DISTINCT t1.id
682+
FROM t1 JOIN
683+
(
684+
SELECT t2.id FROM t2 JOIN t3
685+
ON t3.id = t2.c
686+
WHERE (t3.a > 2 AND t3.b = 2)
687+
GROUP BY t2.id
688+
) m2 ON m2.id = t1.id
689+
) dt;
690+
691+
drop table t1, t2, t3;
692+
SET optimizer_switch= @save_optimizer_switch;
693+
667694
--echo # End of 10.11 tests
668695
SET GLOBAL innodb_stats_persistent=@save_innodb_stats_persistent;

sql/sql_derived.cc

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ typedef bool (*dt_processor)(THD *thd, LEX *lex, TABLE_LIST *derived);
4141
static bool mysql_derived_init(THD *thd, LEX *lex, TABLE_LIST *derived);
4242
static bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived);
4343
static bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived);
44+
static bool mysql_derived_optimize_stage2(THD *thd, LEX *lex,
45+
TABLE_LIST *derived);
4446
static bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived);
4547
static bool mysql_derived_create(THD *thd, LEX *lex, TABLE_LIST *derived);
4648
static bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived);
@@ -58,6 +60,7 @@ dt_processor processors[]=
5860
&mysql_derived_create,
5961
&mysql_derived_fill,
6062
&mysql_derived_reinit,
63+
&mysql_derived_optimize_stage2
6164
};
6265

6366
/*
@@ -109,8 +112,8 @@ mysql_handle_derived(LEX *lex, uint phases)
109112
{
110113
if (!cursor->is_view_or_derived() && phases == DT_MERGE_FOR_INSERT)
111114
continue;
112-
uint8 allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE :
113-
DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);
115+
uint allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE :
116+
DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);
114117
/*
115118
Skip derived tables to which the phase isn't applicable.
116119
TODO: mark derived at the parse time, later set it's type
@@ -170,7 +173,7 @@ bool
170173
mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases)
171174
{
172175
bool res= FALSE;
173-
uint8 allowed_phases= (derived->is_merged_derived() ? DT_PHASES_MERGE :
176+
uint allowed_phases= (derived->is_merged_derived() ? DT_PHASES_MERGE :
174177
DT_PHASES_MATERIALIZE);
175178
DBUG_ENTER("mysql_handle_single_derived");
176179
DBUG_PRINT("enter", ("phases: 0x%x allowed: 0x%x alias: '%s'",
@@ -1071,6 +1074,43 @@ bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived)
10711074
}
10721075

10731076

1077+
/*
1078+
@brief
1079+
Call JOIN::optimize_stage2_and_finish() for all child selects that use
1080+
two-phase optimization.
1081+
*/
1082+
1083+
static
1084+
bool mysql_derived_optimize_stage2(THD *thd, LEX *lex, TABLE_LIST *derived)
1085+
{
1086+
SELECT_LEX_UNIT *unit= derived->get_unit();
1087+
SELECT_LEX *first_select= unit->first_select();
1088+
SELECT_LEX *save_current_select= lex->current_select;
1089+
bool res= FALSE;
1090+
1091+
if (derived->merged || unit->is_unit_op())
1092+
{
1093+
/*
1094+
Two-phase join optimization is not applicable for merged derived tables
1095+
and UNIONs.
1096+
*/
1097+
return FALSE;
1098+
}
1099+
1100+
lex->current_select= first_select;
1101+
/* Same condition as in mysql_derived_optimize(): */
1102+
if (unit->derived && !derived->is_merged_derived())
1103+
{
1104+
JOIN *join= first_select->join;
1105+
if (join && join->optimization_state == JOIN::OPTIMIZATION_PHASE_1_DONE)
1106+
res= join->optimize_stage2_and_finish();
1107+
}
1108+
1109+
lex->current_select= save_current_select;
1110+
return res;
1111+
}
1112+
1113+
10741114
/**
10751115
Actually create result table for a materialized derived table/view.
10761116

sql/sql_select.cc

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,7 +1947,6 @@ bool JOIN::build_explain()
19471947
int JOIN::optimize()
19481948
{
19491949
int res= 0;
1950-
join_optimization_state init_state= optimization_state;
19511950
if (select_lex->pushdown_select)
19521951
{
19531952
// Do same as JOIN::optimize_inner does:
@@ -1960,18 +1959,18 @@ int JOIN::optimize()
19601959
}
19611960
with_two_phase_optimization= false;
19621961
}
1963-
else if (optimization_state == JOIN::OPTIMIZATION_PHASE_1_DONE)
1964-
res= optimize_stage2();
19651962
else
19661963
{
1967-
// to prevent double initialization on EXPLAIN
1964+
/*
1965+
This function may be invoked multiple times. Do nothing if the
1966+
optimization (either full or stage1) are already done.
1967+
*/
19681968
if (optimization_state != JOIN::NOT_OPTIMIZED)
19691969
return FALSE;
19701970
optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS;
19711971
res= optimize_inner();
19721972
}
1973-
if (!with_two_phase_optimization ||
1974-
init_state == JOIN::OPTIMIZATION_PHASE_1_DONE)
1973+
if (!with_two_phase_optimization)
19751974
{
19761975
if (!res && have_query_plan != QEP_DELETED)
19771976
res= build_explain();
@@ -1981,6 +1980,29 @@ int JOIN::optimize()
19811980
}
19821981

19831982

1983+
/*
1984+
@brief
1985+
Call optimize_stage2() and save the query plan.
1986+
*/
1987+
1988+
int JOIN::optimize_stage2_and_finish()
1989+
{
1990+
int res= 0;
1991+
DBUG_ASSERT(with_two_phase_optimization);
1992+
DBUG_ASSERT(optimization_state == OPTIMIZATION_PHASE_1_DONE);
1993+
1994+
if (optimize_stage2())
1995+
res= 1;
1996+
else
1997+
{
1998+
if (have_query_plan != JOIN::QEP_DELETED)
1999+
res= build_explain();
2000+
optimization_state= JOIN::OPTIMIZATION_DONE;
2001+
}
2002+
return res;
2003+
}
2004+
2005+
19842006
/**
19852007
@brief
19862008
Create range filters objects needed in execution for all join tables
@@ -2691,6 +2713,19 @@ JOIN::optimize_inner()
26912713
}
26922714

26932715

2716+
/*
2717+
@brief
2718+
In the Stage 1 we've picked the join order.
2719+
Now, refine the query plan and sort out all the details.
2720+
The choice how to handle GROUP/ORDER BY is also made here.
2721+
2722+
@detail
2723+
The main reason this is a separate function is Split-Materialized
2724+
optimization. There, we first consider doing non-split Materialization for
2725+
a SELECT. After that, the parent SELECT will attempt doing Splitting in
2726+
multiple ways and make the final choice.
2727+
*/
2728+
26942729
int JOIN::optimize_stage2()
26952730
{
26962731
ulonglong select_opts_for_readinfo;
@@ -2711,7 +2746,7 @@ int JOIN::optimize_stage2()
27112746
if (make_range_rowid_filters())
27122747
DBUG_RETURN(1);
27132748

2714-
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE))
2749+
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE_STAGE2))
27152750
DBUG_RETURN(1);
27162751

27172752
if (optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_WITH_KEYS))
@@ -3501,7 +3536,7 @@ int JOIN::optimize_stage2()
35013536
some of the derived tables, and never did stage 2.
35023537
Do it now, otherwise Explain data structure will not be complete.
35033538
*/
3504-
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE))
3539+
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE_STAGE2))
35053540
DBUG_RETURN(1);
35063541
}
35073542
/*
@@ -4765,6 +4800,7 @@ bool JOIN::save_explain_data(Explain_query *output, bool can_overwrite,
47654800

47664801
void JOIN::exec()
47674802
{
4803+
DBUG_ASSERT(optimization_state == OPTIMIZATION_DONE);
47684804
DBUG_EXECUTE_IF("show_explain_probe_join_exec_start",
47694805
if (dbug_user_var_equals_int(thd,
47704806
"show_explain_probe_select_id",

sql/sql_select.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,7 @@ class JOIN :public Sql_alloc
16761676
int optimize();
16771677
int optimize_inner();
16781678
int optimize_stage2();
1679+
int optimize_stage2_and_finish();
16791680
bool build_explain();
16801681
int reinit();
16811682
int init_execution();

sql/table.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,9 +2031,13 @@ class IS_table_read_plan;
20312031
#define DT_CREATE 32U
20322032
#define DT_FILL 64U
20332033
#define DT_REINIT 128U
2034-
#define DT_PHASES 8U
2034+
2035+
#define DT_OPTIMIZE_STAGE2 256U
2036+
2037+
/* Number of bits used by all phases: */
2038+
#define DT_PHASES 9U
20352039
/* Phases that are applicable to all derived tables. */
2036-
#define DT_COMMON (DT_INIT + DT_PREPARE + DT_REINIT + DT_OPTIMIZE)
2040+
#define DT_COMMON (DT_INIT + DT_PREPARE + DT_REINIT + DT_OPTIMIZE + DT_OPTIMIZE_STAGE2)
20372041
/* Phases that are applicable only to materialized derived tables. */
20382042
#define DT_MATERIALIZE (DT_CREATE + DT_FILL)
20392043

0 commit comments

Comments
 (0)