Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
10.11
-
None
Description
Re-executing a prepared statement or a stored procedure statement that has nested derived tables or views takes exponential time in the nesting depth. Each level doubles the time. Direct execution, PREPARE and the first EXECUTE are fast. The derived tables need no expressions, a chain that only passes a column through is enough.
Test case (mysqltest)
create table t1 (f1 int);
|
insert into t1 values (1),(2);
|
|
|
--disable_query_log
|
let $levels= 30;
|
let $q= select f1 as s1 from t1;
|
let $i= 1;
|
while ($i < $levels)
|
{
|
let $j= $i;
|
inc $j;
|
let $q= select s$i as s$j from ($q) dt$i;
|
inc $i;
|
}
|
--echo # direct execution: fast
|
eval $q;
|
eval prepare stmt from "$q";
|
--enable_query_log
|
--echo # first execution: fast
|
execute stmt;
|
--echo # second execution: exponential in the nesting depth
|
execute stmt;
|
deallocate prepare stmt;
|
|
|
--disable_query_log
|
eval create procedure p() $q;
|
--enable_query_log
|
call p();
|
--echo # second call: exponential as well
|
call p();
|
drop procedure p;
|
|
|
drop table t1;
|
Test case (plain SQL)
create table t1 (f1 int); |
insert into t1 values (1),(2); |
prepare stmt from 'select s29 as s30 from (select s28 as s29 from (select s27 as s28 from (select s26 as s27 from (select s25 as s26 from (select s24 as s25 from (select s23 as s24 from (select s22 as s23 from (select s21 as s22 from (select s20 as s21 from (select s19 as s20 from (select s18 as s19 from (select s17 as s18 from (select s16 as s17 from (select s15 as s16 from (select s14 as s15 from (select s13 as s14 from (select s12 as s13 from (select s11 as s12 from (select s10 as s11 from (select s9 as s10 from (select s8 as s9 from (select s7 as s8 from (select s6 as s7 from (select s5 as s6 from (select s4 as s5 from (select s3 as s4 from (select s2 as s3 from (select s1 as s2 from (select f1 as s1 from t1) dt1) dt2) dt3) dt4) dt5) dt6) dt7) dt8) dt9) dt10) dt11) dt12) dt13) dt14) dt15) dt16) dt17) dt18) dt19) dt20) dt21) dt22) dt23) dt24) dt25) dt26) dt27) dt28) dt29'; |
execute stmt; -- fast |
execute stmt; -- 6 s, doubles with each extra level |
Timings
10.11 (bf6311c49ec), RelWithDebInfo build, 2 rows:
| depth | direct | PREPARE | 1st EXECUTE | 2nd EXECUTE (derived) | 2nd EXECUTE (views v_i = select from v_(i-1)) |
|---|---|---|---|---|---|
| 20 | <1 ms | <1 ms | <1 ms | 8 ms | 13 ms |
| 24 | <1 ms | 1 ms | <1 ms | 108 ms | 194 ms |
| 28 | <1 ms | 1 ms | 1 ms | 1486 ms | 2690 ms |
| 30 | <1 ms | 1 ms | 1 ms | 6007 ms | 11579 ms |
A stored procedure behaves the same: with depth 26 every first CALL in a connection takes 4-6 ms, and the second CALL in the same connection takes about 400 ms.
Where the time goes
The second EXECUTE spends the time in recursive derived table handling during JOIN::prepare:
mysql_derived_prepare
|
mysql_handle_single_derived
|
TABLE_LIST::handle_derived
|
LEX::handle_list_of_derived
|
st_select_lex::handle_derived
|
mysql_derived_prepare
|
mysql_handle_single_derived
|
TABLE_LIST::handle_derived
|
... (repeats for every nesting level)
|
st_select_lex::handle_derived
|
JOIN::prepare
|
mysql_select
|
handle_select
|
execute_sqlcom_select
|
mysql_execute_command
|
Prepared_statement::execute
|
A guess, not verified: TABLE_LIST::handle_derived() first recurses into the selects of the inner unit, and then mysql_handle_single_derived() -> mysql_derived_prepare() -> JOIN::prepare() of the same unit handles the derived tables of those selects again. That makes two recursive calls per level. On the first execution the inner units are already merged or marked prepared, so the repeated subtree is skipped.
Found while working on MDEV-31130: under --ps-protocol, its test with 40 levels hung in this code and not in the MDEV-31130 code.
Attachments
Issue Links
- relates to
-
MDEV-31130 INSERT-SELECT with many when/case/if conditions running forever
-
- In Review
-