Skip to content

Commit 49b52a2

Browse files
author
Steinar H. Gunderson
committed
Bug #30110851: SUBQUERY INVOLVES COUNT() AGGREGATE FUNCTION PERFORMANCE REGRESSION
When determining whether to recalculate a materialized derived table for each execution, the uncacheability of the _parent_ query block was used instead of the rematerialize flag on the table (which is currently only true for JSON_TABLE). This could cause unneeded rematerializations, especially when making queries against non-merged views, leading to reduced performance. Do note that there are situations where we do the opposite (materialize where a derived table or CTE is only used once, so it should be streamed); a TODO has been added. (count() has nothing to do with it; the bug title is misleading.) Change-Id: I2b0f20d2323b4f089f67bd0d0c29361156c97190
1 parent c808e7d commit 49b52a2

File tree

8 files changed

+75
-40
lines changed

8 files changed

+75
-40
lines changed

mysql-test/r/derived_correlated.result

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,10 +2057,12 @@ EXPLAIN
20572057
-> Table scan on t1 (cost=0.45 rows=2)
20582058
-> Table scan on dt3
20592059
-> Materialize (invalidate on row from t1)
2060-
-> Stream results
2061-
-> Limit: 1 row(s)
2062-
-> Stream results
2063-
-> Table scan on t2 (cost=0.45 rows=2)
2060+
-> Table scan on dt2
2061+
-> Materialize
2062+
-> Limit: 1 row(s)
2063+
-> Table scan on dt
2064+
-> Materialize
2065+
-> Table scan on t2 (cost=0.45 rows=2)
20642066

20652067
Warnings:
20662068
Note 1276 Field or reference 'test.t1.a' of SELECT #4 was resolved in SELECT #1
@@ -3365,3 +3367,24 @@ id c
33653367
20 20,20
33663368
30 NULL
33673369
DROP TABLE t1, t2;
3370+
#
3371+
# Bug #30110851: SUBQUERY INVOLVES COUNT() AGGREGATE FUNCTION PERFORMANCE REGRESSION
3372+
#
3373+
CREATE TABLE t1 ( f1 INTEGER NOT NULL, f2 INTEGER NOT NULL );
3374+
CREATE TABLE t2 ( f1 INTEGER NOT NULL, f2 INTEGER NOT NULL );
3375+
CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t1;
3376+
CREATE ALGORITHM=TEMPTABLE VIEW v2 AS SELECT ( SELECT f2 FROM v1 WHERE v1.f1 = t2.f1 ) AS f3 FROM t2;
3377+
EXPLAIN FORMAT=TREE SELECT * FROM v2 WHERE f3 = 3;
3378+
EXPLAIN
3379+
-> Index lookup on v2 using <auto_key0> (f3=3)
3380+
-> Materialize
3381+
-> Table scan on t2 (cost=0.35 rows=1)
3382+
-> Select #3 (subquery in projection; dependent)
3383+
-> Index lookup on v1 using <auto_key0> (f1=t2.f1)
3384+
-> Materialize
3385+
-> Table scan on t1 (cost=0.35 rows=1)
3386+
3387+
Warnings:
3388+
Note 1276 Field or reference 'test.t2.f1' of SELECT #3 was resolved in SELECT #2
3389+
DROP TABLE t1, t2;
3390+
DROP VIEW v1, v2;

mysql-test/t/derived_correlated.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,3 +930,24 @@ SELECT * FROM t1 JOIN LATERAL (
930930
) d0 ON (1);
931931

932932
DROP TABLE t1, t2;
933+
934+
--echo #
935+
--echo # Bug #30110851: SUBQUERY INVOLVES COUNT() AGGREGATE FUNCTION PERFORMANCE REGRESSION
936+
--echo #
937+
938+
CREATE TABLE t1 ( f1 INTEGER NOT NULL, f2 INTEGER NOT NULL );
939+
CREATE TABLE t2 ( f1 INTEGER NOT NULL, f2 INTEGER NOT NULL );
940+
CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t1;
941+
CREATE ALGORITHM=TEMPTABLE VIEW v2 AS SELECT ( SELECT f2 FROM v1 WHERE v1.f1 = t2.f1 ) AS f3 FROM t2;
942+
943+
#
944+
# Both materializations here should be marked as non-rematerialize
945+
# (ie., not “Temporary table”). In particular, the materialization
946+
# in the SELECT clause should be reused for each iteration, even though
947+
# the index lookup against it is outer-correlated, and it is part of
948+
# a query block which is also itself outer-correlated.
949+
#
950+
EXPLAIN FORMAT=TREE SELECT * FROM v2 WHERE f3 = 3;
951+
952+
DROP TABLE t1, t2;
953+
DROP VIEW v1, v2;

sql/composite_iterators.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ bool MaterializeIterator::Init() {
813813
}
814814

815815
if (m_unit != nullptr) {
816-
m_unit->clear_corr_ctes();
816+
m_unit->clear_correlated_query_blocks();
817817
}
818818

819819
// If we are removing duplicates by way of a hash field

sql/item_subselect.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3046,8 +3046,7 @@ bool subselect_indexsubquery_engine::exec(THD *) {
30463046
subquery doesn't go through unit::execute() or JOIN::reset(), we have to
30473047
do manual clearing:
30483048
*/
3049-
item->unit->clear_corr_ctes();
3050-
tab->join()->clear_corr_derived_tmp_tables();
3049+
item->unit->clear_correlated_query_blocks();
30513050
if (!table->materialized) {
30523051
THD *const thd = table->in_use;
30533052
bool err = tl->create_materialized_table(thd);

sql/parse_tree_nodes.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ class PT_common_table_expr : public Parse_tree_node {
343343
*/
344344
Common_table_expr m_postparse;
345345

346-
friend bool SELECT_LEX_UNIT::clear_corr_ctes();
346+
friend bool SELECT_LEX_UNIT::clear_correlated_query_blocks();
347347
};
348348

349349
/**
@@ -419,7 +419,7 @@ class PT_with_clause : public Parse_tree_node {
419419
*/
420420
const TABLE_LIST *m_most_inner_in_parsing;
421421

422-
friend bool SELECT_LEX_UNIT::clear_corr_ctes();
422+
friend bool SELECT_LEX_UNIT::clear_correlated_query_blocks();
423423
};
424424

425425
class PT_select_item_list : public PT_item_list {

sql/sql_executor.cc

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,26 +1766,6 @@ unique_ptr_destroy_only<RowIterator> GetTableIterator(
17661766
ConvertItemsToCopy(unit->get_field_list(),
17671767
qep_tab->table()->visible_field_ptr(), tmp_table_param,
17681768
subjoin);
1769-
1770-
bool rematerialize = qep_tab->rematerialize;
1771-
if (qep_tab->join()->select_lex->uncacheable &&
1772-
qep_tab->table_ref->common_table_expr() == nullptr) {
1773-
// If the query is uncacheable, we need to rematerialize it each and
1774-
// every time it's read. In particular, this can happen for
1775-
// outer-correlated derived tables.
1776-
//
1777-
// For (outer-correlated) CTEs, we don't need this check, as we already
1778-
// explicitly clear CTEs when we start executing the query block where
1779-
// it is defined (clear_corr_ctes(), called whenever we start a query
1780-
// block or materialize a table, takes care of this). In fact,
1781-
// rematerializing every time is actively harmful, as it would risk
1782-
// clearing out a temporary table that an outer query block is still
1783-
// scanning. We don't want to set it for a LATERAL derived table either,
1784-
// as we only want to rematerialize it when the previous tables' rows
1785-
// change.
1786-
rematerialize = true;
1787-
}
1788-
17891769
bool copy_fields_and_items_in_materialize = true;
17901770
if (unit->is_simple()) {
17911771
// See if AggregateIterator already does this for us.
@@ -1805,14 +1785,21 @@ unique_ptr_destroy_only<RowIterator> GetTableIterator(
18051785
thd, unit->release_query_blocks_to_materialize(), qep_tab->table(),
18061786
move(qep_tab->iterator), qep_tab->table_ref->common_table_expr(),
18071787
unit, /*subjoin=*/nullptr,
1808-
/*ref_slice=*/-1, rematerialize, tmp_table_param->end_write_records);
1788+
/*ref_slice=*/-1, qep_tab->rematerialize,
1789+
tmp_table_param->end_write_records);
18091790
} else if (qep_tab->table_ref->common_table_expr() == nullptr &&
1810-
rematerialize && qep_tab->using_table_scan()) {
1791+
qep_tab->rematerialize && qep_tab->using_table_scan()) {
18111792
// We don't actually need the materialization for anything (we would
18121793
// just reading the rows straight out from the table, never to be used
18131794
// again), so we can just stream records directly over to the next
18141795
// iterator. This saves both CPU time and memory (for the temporary
18151796
// table).
1797+
//
1798+
// NOTE: Currently, qep_tab->rematerialize is true only for JSON_TABLE.
1799+
// We could extend this to other situations, such as the leftmost
1800+
// table of the join (assuming nested loop only). The test for CTEs is
1801+
// also conservative; if the CTEs is defined within this join and used
1802+
// only once, we could still stream without losing performance.
18161803
table_iterator = NewIterator<StreamingIterator>(
18171804
thd, unit->release_root_iterator(), &subjoin->tmp_table_param,
18181805
qep_tab->table(), copy_fields_and_items_in_materialize);
@@ -1821,11 +1808,11 @@ unique_ptr_destroy_only<RowIterator> GetTableIterator(
18211808
thd, unit->release_root_iterator(), tmp_table_param, qep_tab->table(),
18221809
move(qep_tab->iterator), qep_tab->table_ref->common_table_expr(),
18231810
select_number, unit, /*subjoin=*/nullptr,
1824-
/*ref_slice=*/-1, copy_fields_and_items_in_materialize, rematerialize,
1825-
tmp_table_param->end_write_records);
1811+
/*ref_slice=*/-1, copy_fields_and_items_in_materialize,
1812+
qep_tab->rematerialize, tmp_table_param->end_write_records);
18261813
}
18271814

1828-
if (!rematerialize) {
1815+
if (!qep_tab->rematerialize) {
18291816
MaterializeIterator *materialize =
18301817
down_cast<MaterializeIterator *>(table_iterator->real_iterator());
18311818
if (qep_tab->invalidators != nullptr) {

sql/sql_lex.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ class SELECT_LEX_UNIT {
907907

908908
bool check_materialized_derived_query_blocks(THD *thd);
909909

910-
bool clear_corr_ctes();
910+
bool clear_correlated_query_blocks();
911911

912912
void fix_after_pullout(SELECT_LEX *parent_select, SELECT_LEX *removed_select);
913913

sql/sql_union.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,9 +1086,14 @@ bool SELECT_LEX_UNIT::explain(THD *explain_thd, const THD *query_thd) {
10861086
}
10871087

10881088
/**
1089-
Empties all correlated CTEs defined in the unit's WITH clause.
1090-
*/
1091-
bool SELECT_LEX_UNIT::clear_corr_ctes() {
1089+
Empties all correlated query blocks defined within the query expression;
1090+
that is, correlated CTEs defined in the expression's WITH clause, and
1091+
correlated derived tables.
1092+
*/
1093+
bool SELECT_LEX_UNIT::clear_correlated_query_blocks() {
1094+
for (SELECT_LEX *sl = first_select(); sl; sl = sl->next_select()) {
1095+
sl->join->clear_corr_derived_tmp_tables();
1096+
}
10921097
if (!m_with_clause) return false;
10931098
for (auto el : m_with_clause->m_list->elements()) {
10941099
Common_table_expr &cte = el->m_postparse;
@@ -1402,7 +1407,7 @@ bool SELECT_LEX_UNIT::ExecuteIteratorQuery(THD *thd) {
14021407
Opt_trace_array trace_steps(trace, "steps");
14031408

14041409
if (is_executed()) {
1405-
if (clear_corr_ctes()) return true;
1410+
if (clear_correlated_query_blocks()) return true;
14061411

14071412
// TODO(sgunders): Most of JOIN::reset() should be done in iterators.
14081413
for (SELECT_LEX *sl = first_select(); sl; sl = sl->next_select()) {
@@ -1573,7 +1578,7 @@ bool SELECT_LEX_UNIT::execute(THD *thd) {
15731578
}
15741579

15751580
if (is_executed()) {
1576-
if (clear_corr_ctes()) return true;
1581+
if (clear_correlated_query_blocks()) return true;
15771582
for (SELECT_LEX *sl = first_select(); sl; sl = sl->next_select()) {
15781583
if (sl->join->is_executed()) {
15791584
thd->lex->set_current_select(sl);

0 commit comments

Comments
 (0)